반응형

C언어에서 Pointer를 사용하는 가장 중요한 것 중의 하나는 동적 메모리 할당을 위한 것입니다. 배열의 경우는 변수의 선언과 함께 크기가 정해집니다. 배열은 얼마 만큼의 데이터를 처리할 지 모르는 경우에는, 사용할 것으로 판단되는 최대의 크기를 배열의 크기를 잡아야 되는 문제가 있습니다. 대부분의 경우에 100개 이내를 사용하는 데, 최악의 경우 1,000,000개까지 사용할 수 있다고 하면 1,000,000으로 크기를 잡을 수 밖에 없습니다. 이처럼 크기를 얼마로 잡아야 될 지 알 수 없는 경우에 동적 메모리 할당은 매우 중요합니다. 동적 메모리를 할당하는 메모리 영역은 Heap 메모리라고 하며, 논리적으로 가장 큰 메모리 영역입니다. stack 영역의 메모리는 local 변수나 함수의 파라미터로 넘겨진 변수들의 영역으로 적은 양의 메모리 영역을 차지하고 있어서 수십 MB를 사용하게 되면 stack overflow가 발생하면서 비정상 종료가 됩니다. 전역변수는 프로그램의 실행과 함께 생성되는 데이터 영역의 변수로서 실행파일의 크기에 영향을 줍니다.

 

초기화된 전역변수와 초기화되지 않은 전역변수는 서로 다르게 동작을 합니다. 예를들어

int value1[1024 * 1024 * 10];
int value2[1024 * 1024 * 10] = {0, };

value1은 초기화 되지 않은 전역변수이고, value2는 초기화된 전역변수입니다. 이 들 변수 value1, value2는 0으로 모두 초기화가 됩니다. 차이점은 value1은 실행시에 크기와 초기화가 0으로 됩니다. value2는 초기화된 전역변수이므로 컴파일시에 크기와 초기화가 됩니다. 따라서 초기화된 전역변수는 10MB를 차지하게 되어, 실행 파일의 크기가 10MB 증가하게 됩니다.

 

전역변수의 데이터 영역과 local 변수의 stack 영역은 기본적으로 변수를 선언을 함수 내부이냐 밖이냐의 차이에서 결정이 됩니다. 이들 변수들의 공통점은 데이터를 저장할 공간에 대한 위치(메모리 번지)를 변수 선언에 의해서 결정이 된다는 것입니다. 즉, &변수명하면 곧 그 데이터가 저장되어 있는 메모리 번지가 됩니다. 그러나 Heap 영역은 어디가 될 지 모릅니다. Pointer 변수의 역할는 데이터가 저장된 주소를 저장하는 것입니다. 그래서 Heap 메모리에 최적화된 변수가 곧 Pointer가 되는 것입니다.


Heap 메모리의 할당

Heap 메모리의 할당은 malloc(3)함수 또는 calloc(3)함수를 통해서 메모리를 할당할 수 있습니다.

#include <stdlib.h> 

void *malloc(size_t size);
void *calloc(size_t nmemb, size_t size);

두 함수의 prototype은 위와 같습니다.

int *iptr = (int *)malloc(sizeof(int) * 100);
memset(iptr, 0x00, sizeof(int) * 100);

int *iptr = (int *)calloc(100, sizeof(int));

위의 두 문장은 같은 의미입니다.

 

malloc(3)함수는 초기화를 하지 않으며, calloc(3)함수는 초기화를 하게 됩니다. 메모리 할당과 관련해서 성능 면에서는 malloc(3)함수가 낫고, 초기화까지 해야 하는 경우에는 calloc(3)을 사용하는 것이 성능이 더 좋습니다.

 

Heap 메모리의 해제

만약, 더 이상 사용하지 않는다면, 메모리 해제를 반드시 해줘야 합니다.

free(iptr);

위와 같이 free(3)함수를 통하여 메모리의 할당을 해제합니다. 물론 메모리 해제를 하지 않고 프로그램이 종료를 하게 되면 자동으로 메모리가 해제가 되긴 합니다만, 항상 메모리를 해제하도록 하는 것이 좋습니다.

 


 

iptr에 메모리를 할당한 시작 번지를 대입하는 것으로 iptr은 이제 부터 마치 배열처럼 사용이 가능합니다.

int *iptr = (int *)malloc(sizeof(int) * 100);

int iptr[100]; 

한 것과 비슷합니다.

 

따라서 Pointer를 사용할 때에

int *ptr = (int *)malloc(sizeof(int) * 100);

ptr[0] = 100;
ptr[1] = 200;
ptr[1]++;

와 같이 배열 처럼 똑같이 사용하면 되며, 이렇게 사용하는 것이 안정적으로 사용하는 방법입니다.

int *iptr = (int *)malloc(sizeof(int) * 100);
int *ptr  = iptr;

// 할당한 영역에 1 ~ 100까지 채우기
for(int i = 0; i < 100; i++) {
    iptr[i] = i + 1;
}

printf("%d번째 데이터 = %d\n", 2, iptr[2]);
printf("%d번째 데이터 = %d\n", 2, *(iptr + 2));
ptr += 2;
printf("%d번째 데이터 = %d\n", 2, *ptr);

결과>
2번째 데이터 = 3
2번째 데이터 = 3
2번째 데이터 = 3

위에 결과와 같이 iptr[2] == *(iptr + 2)입니다.

iptr은 int *으로 정의하였기 때문에, iptr에 + 2하면 iptr의 메모리 번지에서 2증가한 번지가 아닌 sizeof(int) * 2한 번지를 뜻합니다. 포인터의 +, -, ++, --, +=, -= 등과 같은 연산은 단순히 그 숫자만큼 메모리 번지를 증감하는 것이 아니라 그 메모리 번지에 저장된 데이터 type의 크기만큼 증감하게 됩니다.

int *ptr = (int *)malloc(100 * sizeof(int));

......

printf("ptr     = %p\n", ptr);
printf("ptr + 1 = %p\n", ptr + 1);

결과)
ptr     = 0x1c44010
ptr + 1 = 0x1c44014 

위와 같이 실행해보면, ptr과 ptr + 1 값의 차이가 4만큼 차이가 나는 것을 알 수 있습니다. 즉, sizeof(데이터 type)만큼의 증감이 일어나는 것을 알 수 있습니다.

 

포인터 변수에 malloc(3) 등과 같은 함수로 메모리를 할당을 받은 후에는 절대로 다른 변수나 새로 메모리 할당을 받으면 안됩니다.

예를들어

double d;
double *dptr = (double *)malloc(sizeof(dobule) * 100);
...
dptr = (double *)malloc(sizeof(dobule) * 300);
...
dptr = &d;

했을 때, dptr 변수에 100 * 8 크기의 메모리를 할당받은 후에 , dptr에 300 * 8의 메모리를 다시 할당 받으면, 기존의 100 * 8 크기의 메모리는 leak이 발생하게 됩니다. 즉, 최초에 할당한 메모리의 위치를 다시 찾아갈 수 없어서 메모리의 해제나 데이터 억세스를 할 수 없게 됩니다. 그 메모리는 할당만 하고 사용하지 않는 미사용의 상

태로, 전체 시스템 리소스를 활용하지 못하게 하며 프로그램이 종료할 때까지 해제할 수 없게 됩니다.

 

 

 

 

see also:

 

memory 관련 Library

memory 관리 함수 malloc(3) - heap memory 할당하기 calloc(3) - 메모리 할당 및 초기화 수행 realloc(3) - 메모리의 크기를 재할당하기 free(3) - 할당된 heap 메모리 해제

www.it-note.kr

 

C Programming Language 문법

1. C 프로그래밍 언어는? 2. C언어 개발 환경 (실습 환경) 3. C언어의 컴파일 과정 4. C 소스 파일 구성 5. 주석문(Comment) 6. 식별자 명명 규칙 7. C 프로그래밍의 시작 - 함수 8. 변수와 상수 (정수형) 9. 변..

www.it-note.kr

 

반응형
블로그 이미지

자연&사람

행복한 개발자 programmer since 1995.

,