프린세스 다이어리

[C] 동적 메모리 할당이 무엇인가요? 개념, 함수, 활용방법 정리 본문

C, C++

[C] 동적 메모리 할당이 무엇인가요? 개념, 함수, 활용방법 정리

개발공주 2021. 9. 30. 12:27
728x90

1. 동적 메모리 할당의 개념과, 함수 malloc()

 

일반적으로는 C에서 배열을 선언할 때 사전에 적절한 크기만큼 할당해주어야 한다.

int a[20] = "hello world";

그런데 항상 사전에 메모리를 할당하는 게 아니라, 프로그램 실행 도중에 필요할 때 메모리를 할당해야 할 때가 있을 것이다. 그 경우에 동적 메모리 할당을 사용해야 한다. C에서는 malloc() 함수를 이용해 원하는 만큼의 메모리 공간을 확보할 수 있다. 

 

포인터 = malloc(할당할 바이트 크기);

Memory allocation의 약자인 malloc 함수는 메모리 할당에 성공하면 주소를 반환하고, 그렇지 않으면 null을 반환한다. 현재 컴퓨터의 메인 메모리, 즉 램을 확인한 다음, 거기서 현재 할당하고자 하는 메모리를 확보할 수 있으면 할당 후 주소를 반환하는 것이다. 

 

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

int main(void) {  
    int *a = malloc(sizeof(int));
    printf("%d\n", a);
    
    a = malloc(sizeof(int));
    printf("%d\n", a);
}

malloc 함수를 이용하여, 하나의 int 만큼인 4 bytes를 할당해서, 할당에 성공하면 메모리 주소를 포인터 a에 넣고 그렇지 못하면 null을 출력하게 했다.

 

main.c:6:20: warning: format specifies type 'int' but the argument has type 'int *' [-Wformat]
    printf("%d\n", a);
            ~~     ^
main.c:9:20: warning: format specifies type 'int' but the argument has type 'int *' [-Wformat]
    printf("%d\n", a);
            ~~     ^
2 warnings generated.
-1010804512
-1010804496

 

일단 오류 메시지는 무시하고 지금 처음 할당됐을 때는 -1010804512, -1010804496 주소가 출력됐다. 처음과 두 번째의 메모리 주소가 다르다. 다시 프로그램을 실행해 보면,

1178622176
1178622192

아예 모두 주소가 바뀐다. 동적 메모리 할당을 수행한다는 것은 운영체제가 그때그때 남아 있는 메모리를 할당해 준다는 의미다.

 

2. 동적 메모리 해제 함수 free()

 

앞서 컴퓨터의 메모리 영역 4가지를 정리해 보았다. 동적으로 할당된 변수는 4가지 중 힙 영역에 저장이 된다. 

 

코드 영역 한 줄 한 줄 실행할 수 있는 소스코드
데이터 영역 변수 중에서 전역 변수와 정적 변수를 담고 있다.
힙 영역 동적 할당 변수를 담는다.
스택 영역 함수마다 담고 있는 지역변수, 매개변수 등을 담고 있다. 

 

스택 영역에 선언된 변수는 따로 메모리 해제를 해 주지 않아도, 해당 블록이 종료가 되는 동시에 비워진다. 그런데 힙 영역에 있는 동적으로 메모리가 할당된 변수는 반드시 free() 함수로 메모리 해제를 해 주지 않으면 메모리 내에 프로세스 무게가 계속 쌓여 메모리 누수가 발생하여 프로그램이 갑자기 종료되는 주요한 원인이 된다. malloc() 함수와 free() 함수는 세트로 다닌다.

 

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

int main(void) {  
    int *a = malloc(sizeof(int));
    printf("%d\n", a);
    free(a);
    
    a = malloc(sizeof(int));
    printf("%d\n", a);
    free(a);
}

앞서 작성한 코드에 free() 함수를 추가해 a의 메모리를 해제한 후, 다시 메모리를 할당해보았다. 

 

-1556064032
-1556064032

위아래 a가 똑같은 메모리 주소를 가리킨다. 비어 있는 주소를 다시 할당할 확률이 높기 때문이다. 

 

3. 동적으로 문자열 메모리 할당하는 법

 

일괄적인 범위의 메모리를 모두 특정한 문자나 숫자로 설정하기 위해서는 memset() 이라는 함수를 사용한다.

memset(포인터, 값, 크기);

 

1byte씩 값을 저장하므로 문자열 배열의 처리 방식과 비슷하다. 예시를 들어 보겠다.

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

int main(void) {  
    char *a = malloc(100);
    memset(a, 'A', 100);

    for (int i = 0; i < 100; i++)
    {
        printf("%c ", a[i]);
    } 
}
A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A 
A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A 
A A A A A A A A A A A A A A A A %

임의로 결과물을 엔터처리했다. 계속 앞으로 가기 뒤로 가기 모션이 적용돼서.. 

*a라는 포인터를 만들어서, 총 100바이트 만큼의 공간을 확보해줬다. 이것을 'A'라는 문자, 즉 아스키코드 65의 값으로 모두 채워 보았다. 이렇게 하면 a라는 문자열 포인터는 A로 채워진다.

 

4. 동적으로 2차원 배열 메모리 할당하는 법

 

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

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

    for (int i = 0; i < 3; i++) {
        *(p + i) = (int*) malloc(sizeof(int) * 3);
    }
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            *(*(p + i) + j) = i * 3 + j;
        }
    }
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            printf("%d ",*(*(p + i) + j));
        }
        printf("\n");
    } 
}

 

int **p = (int**) malloc(sizeof(int*) * 3);

먼저 이중포인터(2차원 배열) p를 만든 다음, 3개의 행만큼 들어갈 수 있도록 만들어준다. 하나의 포인터는 1개의 일차원 배열을 처리할 수 있으니까, sizeof(int*) 자체가 일차원 배열의 시작점을 알려주는 포인터다. 그리고 그게 int* 3개 만큼의 행이 있으니까 총 3개의 행을 가리키는 이중 포인터 p를 만든 거라고 보면 된다.

 

앞에 붙는 (int**) 이게 뭐냐면, malloc은 단순히 메모리만 할당하는 함수이기 때문에 개발자가 리턴값의 데이터형을 지정해줘서 알려줘야 한다. 즉, 원형이 (void*) malloc()인 건데, 우리가 int형 데이터를 저장할 것이기 때문에 (int**)라고 써 주는 것이다. 그리고 그걸 이중포인터 변수 p에 넣어 준다. 참고

 

동적 메모리 할당 그림으로 나타낸 것

 

for (int i = 0; i < 3; i++) {
	*(p + i) = (int*) malloc(sizeof(int) * 3);
}

p+i의 값은 각각의 행을 의미한다. 각각의 행은 또다시 malloc 함수를 이용해 int 3개만큼씩 배열 형태로 만들어 주기 때문에 총 3*3의 2차원 배열이 만들어지게 되는 것이다.

 

for (int i = 0; i < 3; i++) {
  for (int j = 0; j < 3; j++) {
    *(*(p + i) + j) = i * 3 + j;	
  }
}

그 후 이중 for문으로 원소를 넣어 준다. *(p+i), 몇 번째 행에서 (*(p+i)+j), 몇 번째 열인지 알려주고 그 위치의 값을 i*3 + j로 넣어준다. i는 0, 1, 2로 증가하고 j 또한 0, 1, 2로 증가하기 때문에 0에서 8까지의 원소가 차례로 들어가는 것이다.

 

0 1 2 
3 4 5 
6 7 8

이런 식으로, 특정 메모리 할당을 이용해서 배열을 할당하게 되면, 미리 사전에 배열의 크기를 할당해놓지 않아도 필요한 만큼만 그때 그때 할당할 수 있기 때문에 메모리 공간 활용에 효율적이다. 일반적으로 프로그램이 종료되는 순간 메모리가 해제되므로 간단한 예제를 다룰 때는 굳이 free() 함수를 항상 쓸 필요는 없고, 상용 프로그램을 짤 때는 유의해야 한다. 

 

728x90
Comments