본문 바로가기
Basic/C/C++

[C언어] 제 17 강 : 함수의 포인터 활용과 메모리 할당

by boxbop 2012. 1. 24.
반응형


 포인터의 개념은 충분히 이해가 가셨으면 이번엔 포인터의 활용 방법과 메모리에서의 공간 할당을 공부해보겠습니다.

 call by value & call by reference

포인터를 이용하여 함수 내에서 외부에 있는 변수에 직접 접근이 가능하다.

 void CallByValue(int number)
{
    number++;
 }

 void CallByReference(int* ptr)
 {
     (*ptr)++;
 }

 첫 번째 함수와 두 번째 함수의 차이점은 무엇일까요? 가장 눈에 뛰는건 포인터의 사용입니다. 첫 번째는 매개변수를 인자로 사용하지만 두 번째는 포인터를 사용해 주소 값을 매개 변수로 입력 받습니다.

 첫 번째 함수는 임시로 함수 내부에서 number이라는 매개변수를 생성합니다. 즉 인자로 10이라는 수, 또는 10을 저장하는 매개변수를 넘겨주었다면 그 값을 임시적으로 number라는 int형 변수에 저장해 number를 증가시킵니다. 만약 외부 value = 10; 이라는 값, 인자로 value를 넘겨주었다면 실질적으로 value의 값이 증가하는 것이 아니라 number가 value값을 저장해 number값을 증가시키는 것 입니다. 때문에 함수를 빠져나온 후, value의 값은 그대로 10이 되는거죠.

 반면에 두 번째 함수는 value 의 주소값을 인자로 넘겨주기 때문에 함수 내부에서 뿐 만 아니라 외부까지도 영향력을 갖습니다. value의 주소값 자체를 넘겨 주기때문에 value값을 증가시키죠^-^

 첫 번째 함수형태를 값에 의한 호출(call by value)라고 합니다. 값을 복사해서 넘기는 것 뿐이기 때문이죠~ 두 번째 함수형태는 훔수 내에서 주소 값을 참조하여 주소 값이 가리키는 변수의 값을 1증가시킵니다. 이러한 형태를 참조에 의한 호출(call by reference)라고 합니다. 포인터에 의한 호출이라고 부르기도 하죠~~ 상당히 중요한 개념 입니다.

 typedef 키워드
자료형에 새 이름을 부여하는 키워드 입니다. 포인터 개념에는 조금 벗어나지만 후반부에 소개하는 메모리 관련 함수들의 이해를 위해서 간단하게 살펴보겠습니다.

    typedef       TYPE            NAME;
     키워드       자료형     자료형의 새 이름

예를 들어 보겠습니다~

 typedef int INT; 라고 하면 int라는 자료형과 우리가 새로 정의한 INT라는 자료형이 동일합니다. 때문에 int number 이나 INT number은 동일합니다.
 tyepdef int asdfasdf; 라고 해도 int 와 asdfasdf는 동일하죠 ^-^ 그렇다고 의미 없이 asdfasdf라고 정하지는 말자구요~ㅋㅋㅋ 예를 들어본 것 뿐이니...ㅋ
 typedef unsigned int UINT; 라고하면 unsigned int 형을 UINT로 재 정의 한 것 입니다. 의미는 당연히 동일하구요. unsigned int를 UINT로 줄여 사용하기 때문에 좀 더 편리하기는 하겠죠?
 마찬가지로 포인터형 변수도 typedef 정의가 가능합니다. typedef int* P_INT;라고하면 int* 와 P_INT는 동일하겠죠~?
 배열도 역시나 사용 가능합니다만 조금 주의해서 사용하셔야 됩니다. double array[5];라고 하면 길이가 5인 double형 배열의 선인입니다. 하지만 typedef double array[5];라고 선언하면 길이가 5인 double형 배열의 자료형을 의미하게 됩니다. array가 새로운 자료형의 이름이 되구요^-^ 때문에 array arr; 와 double arr[5]는 완전하게 동일합니다. 즉, 배열 선언 앞에 typedef를 붙여주면 배열의 이름이 typedef에 의해 선언된 자료형의 이름으로 인식됩니다.

 메모리 공간의 동적 할당
여기서 기억하셔야 될 부분이 하나 있습니다. 크게 네 개의 영역으로 가상 메모리가 나뉘는 것을 설명한 바 있습니다.

 코드 영역 : 실행할 프로그램의 코드를 올려 놓을 공간
 데이터 영역 : 프로그램이 종료될 때까지 유지해야 할 데이터를 저장할 공간
 스택 영역 : 일시적으로 잠깐 사용하고 삭제할 데이터의 저장공간
 힙 영역 : 프로그래머가 원하는 형태로 쓸 수 있는 공간

 여기서 우리는 힙 영역에 대한 내용을 공부할 것 입니다. 전역 변수는 데이터 영역에 할당이 되어 프로그램이 종료될 때까지 남아있는 변수, 지역변수는 스택에 할당이 되었다가 해당 변수를 선언한 함수가 종료되면 소멸이 되는 변수입니다. 그러나 이 두가지 특성의 변수로는 충족되지 않는 부분이 있는데, 이 부분은 힙 영역을 통해서 해결해야 합니다.

 int* MakeIntArray (int len, int init)
{
    int arr[len];
    for(int i=0 ; i<len ; i++) arr[i]=init;

    return arr;
 }

 위와 같은 함수가 있습니다. 함수의 return값이 배열이 이름(주소 값) 이기 때문에 함수의 반환형은 int*가 됩니다. 함수 내부에 새로운 배열 arr가 선언되고 활용되었습니다. 그리고 이 배열을 리턴합니다. 여기 까지는 문제가 없겠지요. 그러나 이 함수를 빠져나오고 외부에서 이 함수값을 가지고 사용하는 것이 문제가 됩니다. MakeIntArray(5,0)은 배열의 길이가 5이고 모든 요소의 초기 값을 0으로 초기화 시킨 배열을 반환해야되지만 문제가 있습니다. arr라는 배열은 함수 내에만 존재하기 때문에 함수를 벗어나면 소멸되기 때문이죠. 때문에 int* new_arr = MakeIntArray(5,0);과 같은 문장은 사용할 수 없습니다. 함수가 새로운 배열을 만들어서 반환한 후에 new_arr에 저장해야 되지만 반환 하자마자 배열은 없어지기 때문이죠, 즉 함수내에서 정의되어 있기 때문에 함수를 빠져나오면 소멸됩니다. main함수에서 이 주소 값을 가지고 배열에 접근할 시점이면 이미 배열은 소멸된 상태라~~ 이겁니다!!

 이러한 문제점은 배열을 스택에 할당했다는 것 입니다. 함수를 빠져나간 다음에도 할당된 메모리 공간이 소멸되지 않아야 배열을 만들어서 제공하는 함수로서의 역할을 할 수 있습니다. 이러한 문제점을 해결할 수 있는 메모리 공간이 힙 영역입니다. 우리는 원하는 순간에 힙 영역에 메모리 공간을 할당할 수 있고, 원하는 순간에 할당된 메모리의 공간을 소멸시킬 수 있습니다.

 힙 영역에 메모리 공간을 할당하는 함수 malloc
참고로 이 함수는 헤더파일 stdlib.h에 선언되어 있으므로 호출을 위해서 이 헤더 파일을 반드시 포함해줘야 합니다.

 void* malloc(size_t size);  

 성공 시 할당된 메모리의 주소 값, 실패 시 NULL을 반환합니다. size_t는 typedef에 의해 만들어진 자료형입니다. 단순하게 unsigned int, unsigned long라고 생각하시면 되구요 반환형이 포인터라는 것을 기억해 둡시다~

 malloc(12);라고 선언했다면 힙 영역에 12바이트를 할당했다는 이야기 입니다. 어렵지 않죠~? malloc은 인자를 오직 '크기'만 받습니다. 이것이 무슨 이야기 일까요~? 반환되는 주소 값의 포인터 형을 결정하지 못한다는 이야기 입니다. 단순히 12바이트만 할당할 뿐 길이가 3인 int형 1차원 배열일 수 있고, 길이가 12인 char형 문자열 배열일 수 있습니다. 그러나 malloc은 단순히 공간만 할당할 뿐 주소 값의 포인터 형을 결정하지 못한다는 얘기이죠~

 void* ptr = malloc(sizeof(int));
 *ptr =10;

 이 예제에서는 4바이트 int형 변수를 힙에 할당하고자 하는 의도가 담겨있습니다. int형 변수의 크기를 인자로 전달하면서 malloc함수를 호출하고 있습니다. 그리고 void형 포인터 ptr을 선언해서 반환되는 값을 저장합니다. 그러나 *ptr = 10에서 컴파일 에러가 발생합니다. 앞서 void형 포인터는 포인터가 가리키는 대상에 대한 정보 없이 그냥 주소 값만을 저장하고 있는 변수라고 하였습니다. 그러나 10을 저장하는 순간에, 해당 메모리 공간에 4바이트 정수의 형태로 10을 저장해야 될지, 8바이트 정수의 형태로 10을 저장해야될지, 8바이트 실수의 형태로 10을 저장해야 할지 모르는 겁니다!!! 때문에 void형 포인터는 메모리 참조를 위한 * 연산이 불가능합니다.

 void* vPtr = malloc(sizeof(int));
 int* iPtr = (int*)vPtr;
 *iPtr = 10;

 위 예제는 정상 작동을 합니다. void형 포인터를 int형 포인터로 변환하여 사용하였기 때문입니다. 실제 프로그래밍 코드에서는 다음과 같이 사용합니다.

 int* Ptr = (int*) malloc(sizeof(int));

 이해가 좀 가셨나요~? 그렇다면 이렇게 할당된 메모리 공간을 해제하는 방법을 보도록 하겠습니다. 메모리 공간의 해제는 할당보다는 좀 더 간결합니다.

 void free(void* ptr);

 앞서 할당했던 Ptr의 메모리 공간을 해제하기 위해서는 free(Ptr);이라고 해주면 간단하게 해제됩니다~~~ㅋㅋㅋ아후 힘드네요ㅠㅠㅠㅠ 이번 장은 여기까지 하도록 하겠습니다^-^!!!



      
반응형