메모리를 동적으로 할당 받는 이유는 일반 변수는 컴파일 단계에서 변수의 크기가 정해지기 때문에 프로그램이 실행 되면서 얼마만큼의 배열크기를 사용하게 될지 정확히 알 수 없기 때문에 넉넉하게 할당 받는다. 그래서 프로그램이 실행도중에 사용자가 원하는 만큼 메모리를 할당 받을 수 있다면 메모리 낭비가 줄어들고 프로그램도 안정적으로 동작이 된다. 그래서 나온 개념이 메모리 동적 할당이다.

C언어에는 Heap 영역에 사용자가 원하는 크기만큼 메모리를 할당받아 사용할 수 있다.


그것을 지원하는 3가지 함수가 있는데 malloc, calloc, realloc 이다. 이 포스팅에서는 이 3가지 함수에 차이점에 대해서 배워보도록 하자. 기본적으로 malloc 을 많이 사용하지만 다른 함수들도 알아 두면 유용할때가 많다.


그리고 참고로 메모리를 해제하는 함수는 free이다. 메모리를 할당하고 해제하지 않으면 나중에 사용할 heap 영역에 크기가 부족해 프로그램이 정상동작하지 않을 수 있다.


우선 malloc과 free에 기본적인 사용방법은 아래 링크를 참고하자. 정확히 이해하지 않으면 이번 포스팅이 어려울 수 있다.


C/C++ malloc, free를 사용한 메모리 할당 및 해제 : http://hijuworld.tistory.com/59


그리고 메모리 할당과 해제를 하는 원리는 아래 링크를 참고하자.


C, C++ 에서 동적 메모리의 할당과 해제 원리 : http://hijuworld.tistory.com/28





malloc과 calloc의 차이에 대해서 알아보자.


1. 할당받을 메모리 사이즈를 받는 파라미터의 갯수가 다르다.

2. calloc은 메모리 할당을 받고 0으로 초기화 한다.



우선 함수의 선언을 보면 아래와 같다.


void *malloc(size_t size);

void *calloc(size_t num, size_t size);


malloc은 메모리를 할당받을 사이즈 하나만을 파라미터로 받는 반면

calloc은 메모리를 할당받을 사이즈에 그 메모리를 몇개를 할당 받을지 갯수도 파라미터로 받는다.

둘다 void 포인터를 반환하는데 그 이유는 할당한 메모리를 어떤 크기로 잘라서 사용할지는 그때그때 다르기 때문에 사용자가 강제형변환을 통해 사용하면 되기 때문이다. 


아래 간단한 예제를 보자.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <stdlib.h>
 
int main() {        
    int *pi1; 
    int *pi2;
 
    pi1 = (int *)malloc(sizeof(int* 3); //할당
    pi1[0= 1;
    pi1[1= 5;
    pi1[2= 8;
    
    pi2 = (int *)calloc(3sizeof(int)); //할당
    pi2[0= 1;
    pi2[1= 5;
    pi2[2= 8;    
 
    free(pi1); //메모리 해제
    free(pi2); //메모리 해제
}
cs


pi1 은 malloc으로 할당을 받고 pi2 는 calloc으로 할당을 받았다. 

파라미터의 갯수 말고도 더 큰 차이가 있다.

바로 calloc은 메모리를 할당과 동시에 0으로 초기화 하는 것이다.


아래 예제를 보자. 모두 할당만 하고 값을 출력시켜 보았다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
#include <stdlib.h>
 
int main() {        
    int *pi1; 
    int *pi2;
 
    pi1 = (int *)malloc(sizeof(int* 3); //할당
    printf("malloc : %d %d %d\n", pi1[0], pi1[1], pi1[2]);
    
    pi2 = (int *)calloc(3sizeof(int)); //할당
    printf("calloc : %d %d %d\n", pi2[0], pi2[1], pi2[2]);
 
    free(pi1); //메모리 해제
    free(pi2); //메모리 해제
}
cs



출력결과

malloc : -842150451 -842150451 -842150451

calloc : 0 0 0                                          


할당하면서 0으로 초기화가 되기때문에 상황에 따라 편리할 수 있다.


realloc에대해서 알아보자.

realloc은 malloc이나 calloc으로 할당받은 메모리의 사이즈를 변경할 때 사용한다.


함수선언은 아래와 같다.

void *realloc(void *block, size_t size);


파라미터로 기존에 할당받았던 메모리를 전달하고 다음 파라미터로 새로 할당 받을 size를 전달한다.


아래 예제를 보자.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <stdlib.h>
 
int main() 
{        
    int i;
    int *pi = (int *)malloc(sizeof(int* 3); //4x3 메모리 할당
    for (i = 0; i < 3; i++)
        pi[i] = i * 4;
    for (i = 0; i < 3; i++)
        printf("pi[%d] : memory %d %d\n", i, &pi[i], pi[i]);
    printf("-----------------------------\n");
 
    pi = (int *)realloc(pi,sizeof(int* 5); //4x5 메모리 재할당
    for (i = 3; i < 5; i++)
        pi[i] = i * 4;
    for (i = 0; i < 5; i++)
        printf("pi[%d] : memory %d %d\n", i, &pi[i], pi[i]);
    printf("-----------------------------\n");
 
    free(pi); //메모리 해제
}
cs


출력결과

pi[0] : memory 9457232 0  

pi[1] : memory 9457236 4  

pi[2] : memory 9457240 8  

-----------------------------  

pi[0] : memory 9457232 0  

pi[1] : memory 9457236 4  

pi[2] : memory 9457240 8  

pi[3] : memory 9457244 12 

pi[4] : memory 9457248 16 

-----------------------------  


처음 4X3 메모리를 할당받았고 그다음에는 4X5 메모리를 재할당 받았다.

재할당 받은 뒤에 기존에 할당받았던 값 이외에 값인 4번째, 5번째에만 값을 넣어준다.

재할당 받은 뒤에도 기존에 값은 정상적으로 존재하는 것을 확인할 수 있다.

단순히 메모리 사이즈만 변경하는 것이 아닌 내용물도 복사를 한다.

 

그리고 메모리의 주소값이 같은것을 볼 수 있는데 이것은 보장하지는 않는다.

완전 새로운 메모리에 값을 새로 할당받아 값을 복사할 수도 있다.


아래 예제를 보자. 메모리를 기존보다 훨씬 크게 할당한 것을 볼 수 있다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <stdlib.h>
 
int main() 
{        
    int i;
    int *pi = (int *)malloc(sizeof(int* 30000); // 메모리 할당
    for (i = 0; i < 3; i++)
        pi[i] = i * 4;
    for (i = 0; i < 3; i++)
        printf("pi[%d] : memory %d %d\n",i,&pi[i],pi[i]);
    printf("-----------------------------\n");
 
    pi = (int *)realloc(pi,sizeof(int* 5000000); // 메모리 재할당
    for (i = 3; i < 5; i++)
        pi[i] = i * 4;
    for (i = 0; i < 5; i++)
        printf("pi[%d] : memory %d %d\n", i, &pi[i], pi[i]);
    printf("-----------------------------\n");
 
    free(pi); //메모리 해제
}
cs

출력결과

pi[0] : memory 17674192 0 

pi[1] : memory 17674196 4 

pi[2] : memory 17674200 8 

-----------------------------  

pi[0] : memory 21880896 0 

pi[1] : memory 21880900 4 

pi[2] : memory 21880904 8 

pi[3] : memory 21880908 12

pi[4] : memory 21880912 16

----------------------------- 


메모리의 주소값이 달라진 것을 볼 수 있다. 하지만 0, 4, 8은 잘 복사된 것을 볼 수 있다.




Posted by 꿈만은공돌
,

동적 메모리 할당은 사용자가 프로그램이 실행도중 원하는 메모리의 크기를 할당하여 사용하는 것이다.

프로그래머가 코드를 작성할 당시에 정확히 얼마에 메모리가 필요한지 알 수 없기때문에 프로그램 실행도중 필요한 만큼만 할당받기 때문에 메모리 낭비를 줄일수 있다.

포인터가 사용되기 때문에 포인터에 대한 이해가 낮으면 조금 어려울 수 있는 내용이다. 그래도 계속 보다보면 적응이 될 것이다.


C언어에서는 힙영역에 사용자가 메모리를 동적 할당하는 3가지 함수인 malloc, calloc, realloc 이 있고

메모리를 해제하는 함수인 free가 있다.


메모리 할당 및 해제에 관한 원리는 아래 링크를 참고하자.

C, C++ 에서 동적 메모리의 할당(malloc, new)과 해제(free,delete) 원리 : http://hijuworld.tistory.com/28


동적 할당하는 3가지 함수인 malloc, calloc, realloc 의 차이에 대해서는 아래 링크를 참고하자.

C/C++ 동적 메모리할당 malloc, calloc, realloc 함수 비교 및 예제 : http://hijuworld.tistory.com/60





가장 기본인 동적 할당 함수인 malloc에 대해서 알아보자.


#include <stdlib.h> 

void* malloc(size_t size);


선언은 위와 같은 형식이다.

파라미터로 할당할 메모리의 크기를 넘기고 반환형이 void 형 포인터이다.

void 포인터인 이유는 할당받은 메모리를 몇바이트씩 잘라서 사용할지는 그때그때 다르기 때문에 사용자가 형변환을 통해서 사용하면 되기 때문이다. 


간단하게 예제를 보자. 1바이트 메모리를 할당해서 char형 포인터에서 할당한 주소값을 저장한다.

1바이트할당 받은 것을 (char*) 를 사용해서 강제형변환을 한 것을 볼 수 있다. 


1
2
3
4
5
6
7
8
9
#include <stdio.h>
#include <stdlib.h>
 
int main() {        
    char *= (char*)malloc(1); //1바이트 메모리 할당
    *= 'h';
    printf("%d, %c\n",c, *c);//c의 주소값, c의 값 'h'
    free(c); //메모리 해제
}
cs

출력 결과

17674200, h


 할당받은 것을 그림으로 그려보면 아래와 같다. 우선 malloc을 이용해서 힙영역에 0x1000번지에 1바이트에 메모리를 할당 한다. 그리고 스택영역에 선언한 지역변수인 포인터 C가 malloc으로 할당한 주소를 가지고 있는다. 

그리고 해당 포인터를 가지고 *c = 'h'로 malloc으로 할당한 메모리에 문자열을 저장한다.



 파라미터로 사이즈를 넘기기 때문에 위의 예제처럼 직접 숫자를 입력해도 가능하진 하지만 아래 예제와 같이 sizeof 를 써서 할당할 변수의 크기와 그 변수의 갯수를 입력하는것이 휴먼에러를 줄이는 방법이다. char 가 1바이트고 int가 4바이트고 float이 4바이트 라는 보장은 할 수 없다. 컴파일러나 운영체제에 따라서 달라질 수 있기 때문이다. 그래서 가장 안전한 방법인 sizeof() 함수를 이용해서 해당 변수에 크기를 그때그때 검사해주는 방법이 좋다. 


1
2
3
4
5
6
7
8
9
#include <stdio.h>
#include <stdlib.h>
 
int main() {        
    char *= (char*)malloc(sizeof(char)); //char 변수 하나의 메모리 할당
    *= 'h';
    printf("%d, %c\n",c, *c);//c의 주소값, c의 값 'h'
    free(c); //메모리 해제
}
cs

비슷한 예제로 int형 변수 4개를 할당받아보자. int에 크기에 4개를 곱한 사이즈를 할당 받았다.

그리고 (int *)로 형변환을 시켜서 i[1] 등을 했을때 4바이트씩 메모리를 증가시키게 된다. 그래서 아래와 같이 배열처럼 사용을 하면 된다. i[1] 은 *(i+1)과 완벽하게 같은 말이다. 


1
2
3
4
5
6
7
8
9
int main() {        
    int *= (int*)malloc(sizeof(int* 4); //int 사이즈 4개 메모리 할당
    i[0= 5;
    i[1= 1;
    i[2= 10;
    i[3= 20;
    printf("%d %d %d %d\n",i[0],i[1],i[2],i[3]);
    free(i); //메모리 해제
}
cs


출력 결과

5 1 10 20


sizeof(int) * 4를 그림으로 그려보면 아래와 같다.  힙영역에 0x1000번지 부터 총 16바이트에 메모리를 할당 받았다. 그리고 지역변수로 선언산 int형 포인터 변수인 i가 그 시작 주소값을 저장한다. i는 int* 이기 때문에 i는 0x1000번지를 가리키고 i+1을 하면 4바이트가 증가하여 1004번지를 가리키게 된다. 그래서 배열과 같이 사용할 수 있게 된다.


2차원 배열을 메모리로 할당 받아보자.

더블포인터를 이용해서 한번 할당받고 다시 각각의 2차원배열 요소들을 할당 받아야 한다.

아래예제를 보자. 처음보면 상당히 복잡하고 난해할 수 있다. 특히 이중포인터를 사용했기 때문에 쉽게 이해가 가지 않을 수 있다. 이해가 가지 않는다면 우선 해당 코드를 복사해서 사용하고 차차 이해해도 된다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int main() {        
    int **ppi = (int**)malloc(sizeof(int** 3); //int* 사이즈 3개 메모리 할당
    int i,j;
    for(i = 0; i < 3; i++)
        ppi[i] = (int*)malloc(sizeof(int* 2); //int 사이즈 2개 메모리 할당
    
    for (i = 0; i < 3; i++) {
        for (j = 0; j < 2; j++) {
            ppi[i][j] = (i + 1) * (j + 1);
        }
    }
    for (i = 0; i < 3; i++) {
        for (j = 0; j < 2; j++) {
            printf("%d ", ppi[i][j]);
        }
    }
    printf("\n");
    
    for (i = 0; i < 3; i++)
        free(ppi[i]); //메모리 해제
    free(ppi); //메모리 해제
}
cs


출력결과

1 2 2 4 3 6


그리고 메모리 해제역시 2차원배열이기 때문에 각 요소들을 해제하고 2차원배열을 해제해줘야 한다.



Posted by 꿈만은공돌
,

C언어와 C++에서는 new 연산자와 malloc 함수를 이용해서 힙(heap)영역에 동적 메모리 할당을 받고 delete 연산자와 free 함수를 이용해서 동적 메모리를 해제하게 된다.


heap 영역에 메모리를 할당하게 되면 자유메모리 블록 리스트(Free memory block list)로 관리가 된다.


새로운 메모리를 할당 할 때에는 자유메모리 블록 리스트를 참고하여 필요한 만큼의 메모리를 할당 할 수 있는 공간을 찾는다.


찾으면 자유 메모리 블록 리스트에 추가하고 할당한 해당 메모리 공간을 초기화 시킨다.


아래 그림을 보면 쉽게 이해가 될 것이다.



처음 malloc(7000); 으로 7000바이트에 메모리를 할당받는다.

가장 첫번째 A영이있는 앞부분부터 탐색을 시작하여 사용하지 않는 메모리를 찾습는다.

B구역이 사용하지 않는 메모리이지만 크기가 4k밖에 안되게 때문에 계속탐색을 하여 가장 뒷부분인 E영역에 할당을 하게 된다


그리고 free(C) 로 C블록을 메모리를 해제시킵니다. 그러면 C영역에 5000바이트의 메모리가 해제가 된다..

이때 malloc(5000) 으로 메모리를 다시 할당하게 된다.

A부터 해서 다시 빈메모리영역을 찾습니다. G영역이 9k가 비어있기 때문에 앞쪽부터 5000바이트에 메모리를 할당 한다.


이런방식으로 계속 메모리 할당과 해제를 계속 하다보면 메모리 단편화가 일어날 수 있다.

메모리를 할당과 해제를 계속하다보면 중간중간 사용하는 메모리와 사용하지 않는 메모리가 존재하게 되된다. 

아래그림처럼 사용하는 메모리와 사용하지 않는 메모리가 있는데 여기서 300바이트가 넘는 메모리를 할당요청하게 되면 할당이 실패하게 된다. 전체 사용하지 않는 메모리는 850바이트나 되지만 단편화가 일어나서 메모리 할당에 실패하게 된다.



메모리 단편화를 피하려면 메모리를 너무 자주 할당 해제하는 것을 피하는 것이 좋다.


그리고 이와 같은 메모리 할당 방식은 앞에서 부터 빈 메모리영역을 탐색하기 때문에  Heap 메모리 영역을 탐색하는데 많은 시간이 소요될 수 있다. 특히 메모리 크기가 크고 빈 메모리영역이 뒷쪽에 있다면 매번 메모리 할당에 오버헤드가 발생한다.


그리고 할당만 하고 해제를 하지 않으면 메모리가 낭비될 수 있다.


사용자가 직접 해제를 시켜줘야하기 때문에 많은 주의가 필요하다.


이런 사용자가 직접 메모리를 할당하고 해제하는 방식과 반대로 가비지 컬렉터가 사용하지 않는 메모리를 자동으로 해제하는 방식에 가비지 컬렉션이라는 방식이 존재한다.

C++ 이후에 등장한 대부분에 언어에서는 이런 가비지 컬렉션을 사용한다. 예를들면 JAVA, C#, GO 언어 들이 가비지 컬렉션을 사용한다.


C#/.NET 가비지 컬렉션의 메모리 할당 및 해제 기본 원리 : http://hijuworld.tistory.com/32


Posted by 꿈만은공돌
,