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

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

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


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 함수를 이용해서 동적 메모리를 해제하게 된다.


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


하지만 C#과 JAVA 에서는 가비지 컬렉터(garbage collecter)에 의해서 메모리 할당과 메모리 해제가 이루어진다.


사용하지 않는 메모리를 가비지 컬렉션(garbage collection)이 해제 시킨다.


우선 간단하게 메모리를 할당하는 방식에 대해서 알아 보자.




1. C#에서 객체 생성시 메모리 할당 방식

new 키워드를 통해 객체를 생성하게 되면 힙(Heap) 메모리 영역에 객체가 할당된다. C언어와 C++과 다른점은 메모리 첫영역부터 차례대로 할당하게 되고 할당이 가능한 부분의 첫 주소에 포인터로 가르키게 된다. 다음에 또 객체를 생성하면 할당 가능한 메모리의 포인터 영역부터 메모리를 할당하고 다시 해당 포인터는 할당된 사이즈 만큼 증가하게 됩니다.


C, C++과 다르게 이렇게 하면 메모리 할당이 가능한 영역을 찾는 과정이 없기 때문에 속도가 훨씬 빠르다.

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








2. C#에서 Garbage Collector에 의한 메모리 해제 방식

Test a = new TEST();

a = null;

위와 같은 코드를 만나면 처음에 할당한 변수 a가 가지고 있는 객체는 더이상 사용하지 않기 때문에 메모리 해제의 대상이 됩니다.

힙영역에 메모리가 계속 쌓이다 보면 특정 시점에서 Garbage collector에 의해 사용되지 않는 메모리(아래 그림에서 B, D)는 해제하게 된다.

Root Reference 메모리 영역에서 사용하는 메모리 영역을 관리하게 되고(아래 그림의 A와 C) 객체에서 할당한 객체도 존재(아래 그림에서 E)한다. Root Reference를 탐색하여 A와 C를 찾고 다시 A와 C를 검색하여 E를 찾아낸다.

그러면 그외에 메모리 영역인 B와 D는 사용하지 않는 메모리 영역으로보고 메모리를 해제한다.

그리고 남은 A, C, E를 다시 재배치 한다.

그리고 다음 객체를 할당할 메모리의 포인터의 위치도 재배치 한다.




여기까지가 간단하게 설명한 메모리 할당 및 해제 원리이다.

사실 가비지 컬렉션에 방식은 이외에도 상당히 복잡하다.


1. 세대별 가비지 컬렉션 방식 : http://hijuworld.tistory.com/41


2. 용량이 큰 객체에 메모리 할당 해제 방식 : http://hijuworld.tistory.com/46


3. 가비지 컬렉션의 모드에 따른 방식


따라서 이 3가지 방식에 대해서 다음 포스팅에서 설명하겠다.




참고 사이트 및 서적 

- c# GC.Collect() 정리 : http://helloit.tistory.com/40

- 유경상의 닷넷 블로그 : http://www.simpleisbest.net/post/2011/04/01/Review-NET-Garbage-Collection.aspx

- MSDN : https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentals

- 뇌를 자극하는 C# 4.0(한빛미디어, 박상현 지음)





Posted by 꿈만은공돌
,