모노산달로스의 행보

[C programming] 동적 메모리 할당 본문

ProgrammingLanguage/C

[C programming] 동적 메모리 할당

모노산달로스 2024. 4. 18. 19:12

C programming - 동적 메모리 할당

 

 

리눅스 환경에서 네트워크 프로그래밍을 공부하기 위해서 C언어를 다시 복습해야 할 필요성을 느꼈습니다. 따라서 이번 기회에 배열부터 전처리기까지 내용들을 정리하겠습니다.

 


동적 메모리란?

출처 : https://www.trytoprogram.com/c-programming/dynamic-memory-allocation-in-c/

 

프로세스의 메모리 구조는 위와 같이 4개로 구분됩니다. 그중에서 힙 영역은 프로그램이 실행되는 동안 동적으로 메모리를 할당할 수 있는 영역을 의미합니다. 힙 영역의 특징으로서 프로그래머가 요구할 경우 런타임 중에 동적 메모리 할당이 이루어집니다. 그렇다면 왜 동적 메모리 할당을 요청할까요?

 

int array[5];
array[0] = 10, array[1] = 20, array[2] = 30;

int array[2];
array[0] = 10, array[1] = 20, array[2] = 30;

 

정적으로 메모리를 할당하는 경우 프로그래머가 필요한 메모리 크기를 예측할 수 없는 경우가 발생합니다. 위와 같이 배열의 크기를 다 사용하지 못해 낭비되거나 크기 이상을 사용하여 에러가 발생하는 경우가 그 예시입니다.

 

#include <stdlib.h>
void* malloc (size_t size)
#include <stdlib.h>
void free (void* p)

 

malloc() 함수와 free() 함수를 통해서 메모리를 할당하고 해제할 수 있습니다.

 

#include<stdio.h>
#include<stdlib.h>

int main(void)
{
    int* p = NULL;
    p = (int*)malloc(sizeof(int));

    if(p == NULL)
        printf("힙 영역에 동적 메모리 할당 실패\n");

    *p = 10;

    printf("주소 : %x \n", p);
    printf(" 값 : %d \n",*p);

    free(p);
    p = NULL;
    
    return 0;
}

예제 코드의 출력 결과

 

포인터 변수 p를 선언하고 (int*) malloc(sizeof(int))와 같이 초기화를 하였습니다. 이제 포인터 p는 힙 영역에 할당된 4바이트의 공간을 가리키게 됩니다. 이후 값을 10으로 초기화하는 과정도 문제없이 이어집니다. 동적 메모리 사용이 끝나면 free(p)를 통해서 메모리를 해제해 줍니다.

 

#include<stdio.h>
#include<stdlib.h>

int main(void)
{
    int i = 0;
    int* p = (int*)calloc(4, sizeof(int));

    if(p == NULL)
        printf("힙 영역에 동적 메모리 할당 실패\n");

    for(i=0; i<4; i++) {
        p[i] = i;
        printf("주소: %x \n", &p[i]);
        printf("값: %d \n", p[i]);
    }

    free(p);
    p = NULL;

    return 0;
}

예제 코드의 출력 결과

 

이번에는 malloc() 함수가 아닌 calloc() 함수를 사용했습니다. 해당 함수는 두 개의 인자를 받아서 동적 메모리를 할당합니다. 첫 번째 인수는 메모리의 크기이며 두 번째 인수는 메모리의 데이터 타입을 의미합니다.

 

malloc() 함수와 가장 큰 차이점은 값을 0으로 초기화한다는 것입니다. malloc() 함수의 경우 값을 초기화하지 않으면 0이 아닌 쓰레기 값이 출력되게 됩니다.

 

#include<stdio.h>
#include<stdlib.h>

int main(void)
{
    int i=0;
    
    int* p=(int*) malloc(sizeof(int)*2);
    p[0] = 10;
    p[1] = 20;

    p = (int*) realloc(p, sizeof(int)*4);
    p[2] = 30;
    p[3] = 40;

    for(i=0; i<4; i++)
        printf("p[%d] : %d \n", i, p[i]);

    free(p);
    p = NULL;

    return 0;
}

예제 코드의 출력 결과

 

위에서 보았던 malloc()과 calloc() 함수는 메모리가 할당되면 변경할 수 없습니다. 이러한 점을 보완하기 위해서는 realloc() 함수를 사용할 수 있습니다. 위 예제와 같이 malloc() 함수를 통해 int 자료형을 저장할 2의 크기의 동적 메모리를 할당했습니다. 이후 realloc을 통해서 포인터 p가 가리키는 힙 영역의 크기를 4로 지정합니다. 그렇게 되면 p[2]와 p[3]에 값을 초기화하여도 문제가 없습니다. 즉, 동적으로 할당된 메모리의 크기가 변경된 것입니다.

 

동적 메모리 사용 시 유의점

동적 메모리를 사용할 때는 메모리 누수와 Dangling 포인터 문제를 유의해야 합니다. 

#include<stdio.h>
#include<stdlib.h>

int main(void)
{
    int* a = (int*) malloc(sizeof(int) * 3);
    int* b = (int*) malloc(sizeof(int) * 2);

    b = a;
    free(a);
    free(b);

    return 0;
}

 

위와 같은 예제에서 문제 점은 무엇일까요? a와 b 포인터가 동적 메모리를 할당받았습니다. 그런데 b 포인터가 a 포인터의 메모리 주소를 가리키게 되었습니다. 이후 free를 통해서 메모리를 해제해 주어도 원래 b 포인터가 할당받은 메모리는 해제되지 않습니다.

 

이를 메모리 누수(Memory Leak)이라고 합니다.

 

#include<stdio.h>
#include<stdlib.h>

int main(void)
{
    int* a = (int*) malloc(sizeof(int));

    *a = 10;
    free(a);

    *a = 20;

    return 0;
}

 

위와 같은 경우는 어떤가요? a 포인터가 동적 메모리를 할당받아 10이라는 값을 초기화했습니다. 그리고 동적 메모리를 해제하였는데 포인터 a는 여전히 사라진 메모리를 가리키고 있습니다. 이후 사라진 메모리에 값 20을 할당하는 것은 문제가 발생할 수 있습니다. 이를 Dangling Pointer 즉, 사라진 메모리에 매달려있는 포인터라고 합니다.