본문 바로가기

C언어

Chapter 18. 메모리 할당과 해제

Chapter 18.메모리 할당과 해제

시스템으로부터 메미로를 할당 받는다는 것은 시스템에서 사용하고 있지 않은 메모리 영역을 배정받고 이에 대한 주소를 얻는 것이다.

동적 메모리는 "절대" 변수를 통해서 접근할수없다. 포인터 변수에 의해서만접근이 가능하다.

포인터 변수에 의해서 접근할 수 있는 영역은malloc()에 의해서 생성될 수 있다.

C 프로그램에서의 메모리 구조는 다음과 같다.

1. 텍스트 영역(TextSegment)

CPU에 의해서수행되는 기계어 명령어들이 모여 있는 곳을 지칭한다.

2.초기화된 데이터 영역(Initialized Data Segment)

프로그램에서 초기화된 데이터 들이 모여 있는 장소이다.

int imsi = 100;

3.비 초기화된 데이터 영역(Uninitialized Data Segment)

초기화 하지 않은 변수들에 대한 집합체이다.

int imsi;

위 변수가 함수 바깥쪽에 존재한다면 imsi는 항상0으로 초기화 되며, "비 초기화된 데이터 영역"에 저장된다.

이 부분을BBS(BlockStartedby Sybol)이라 부른다.

4.스택(Stack)

자동 변수들이 저장된 곳이라 할수 있다. 정확히는 자동 변수들과 그에 따른 환경이 같이 저장된다.

자동 변수는 함수가 호출될 때 생성되고 함수가 끝나면 사라지는 변수이다.

따라서, 함수가 호출될때 복귀할 수 있는 주소와 호출 함수의 화경이 스택에 저장된다.

5.힙(Heap)

동적 기억장소를 위한 공간이다.

간단히 말해서 프로그램이 실행되는 중간에 필요에 의해서 할당받는 메모리 영역을 통칭한다고 볼 수 있다. 따라서,크기가 유동적이다.

일반적으로BSS 위쪽에 존재하고 스택보다는 아래쪽에 존재한다.

스택과 힙은 정형화되어 있지않은 공간이다. 프로그램의 수행에 따라서 늘기도 한다.

일반적으로 사용되는 메모리 할당 관련 함수는다음과 같이3가지다.

#include <stdlib.h>

1.malloc()

void *malloc(size_t size);

2. calloc()

void *calloc(size_t nobj, size_tsize);

3.realloc()

void *realloc(void*ptr, size_tnewsize);

동적 메모리 할당의장·단점

장점

어떠한 정보를 저장하려할 때 얼마만큼의 영역을 지정해야 하는지 알 수 없을 때 사용한다.

malloc()은 컴파일 시에 저장 영역을 지정하는 것이 아니라 실행 시에 정할 수 있다.

단점

엄청난 효율성에도 불구하고 시간 지체라는 단점을갖고 있다.

malloc()을 사용하는 순간 시스템은 사용하지 않는 메모리를 할당하고 이에 대한 포인터를리턴할 것이다.

이것은 프로그램의 실행 지체를 유발하므로꼭 사용해야 하는 곳에 적당히 사용해야한다.

malloc()

메모리 힙으로부터 size 바이트 만큼의블록을 할당받으며 이에 대한 주소 값을리턴한다.

만약,할당할 메모리가 없어서실패하면널 포인터를 리턴한다.

다음은 malloc()의 원형이다.

#incude <stdlib.h>

void *malloc(size_t size);

malloc()은 다음과같은 특징을 갖고 있다.

  1. 동적으로 메모리를 할당받을 수 있도록 해준다.
  2. void형 포인터를 리턴하기 때문에 어떠한 형이든 상관하지 않고 메모리를 할당받을 수 있다.
  3. size_t는 typedef문으로 재정의된 형으로써<sys/types.h>에서 찾아볼 수 있다.
    일반적으로 unsigned long이나long으로 정의되어 있으며 시스템마다 약간 차이가 있다.
가장 간단한 메모리 할당

char *func()

{

char*imsi;

imsi = malloc(100);

...

return imsi;

}

malloc()에의해서 할당된 100byte중 가장 첫번째 byte의 주소가 imsi에 들어가며 func() 함수는 이 주소를 호출 함수에 리턴해주고 있다.

주의할 것은malloc()이 func() 함수 안에서 사용되었기 때문에func()가 끝나는 순간 할당된 메모리가 사라지는 것으로 착각해서는 안된다.

메모리 해제는순전히 프로그래머의 몫이다. 반드시 이에 대한 처리를 해주어야 한다.

main()

{

char *imsi;

imsi = malloc(100);

temp(imsi, 100);

free(imsi);

}

malloc()을 이용하여 메모리를 할당하고temp() 함수에서 이용한 다음 free()를 이용하여 해제하고 있다.

사실 위main()에서는 free()가 필요없다프로그램이 종료되면 메모리가 해제되기때문이다.하지마,malloc()을 사용하면free()를 해줘야 한다는것을 보여주는 것이다.

하지만, 위의func() 함수는 malloc()이func()에 감춰져 있기 때문에 free()를 해주어야 하는 시점을 명확히할 수가 없다.

따라서, 특정한 함수 안에 초기화(메모리 할당이나 특정한 값할당)하는 함수를 따로 마련하거나 malloc() 부분을 따로 작성하는 것이 메모리 누수를 막는 좋은 습관이다.

동적 메모리 영역 초기화

malloc()은 동적으로 메모리 할당만 할 뿐 그 안의 영역을 초기화 해주진 않는다.

할당받은 영역을 초기화 해주기 위해서 사용되는 함수는 memset()이며사용법은 다음과 같다.

문자열

char *imsi;

imsi =(char *)malloc(100 + 1);

memset(imsi, 0,100 + 1);

구조체

struct grades {

char*name;

int grade;

} grade;

struct grades *grade;

grade =(struct gradesc *)malloc(sizeof(structgradesc));

memset(grade,0, sizeof(struct gradese));

할당 가능한 메모리 영역 알아보기

18_1.c

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

main()
{
int Mbyte = 0;
char *imsi;

while((imsi = (char *)malloc(1 << 20))) Mbyte++;

printf("Total : %d\n", Mbyte);
}

gcc -o 18_1 18_1.c

./18_1

Total : 3055

1 << 20은 2의 20승이며 while()을 한번 회전할 때마다 1MB씩 증가한다.

malloc()에 실패하면 NULL을 리턴하믈 while()을빠져나오게 되며이때의 MB를 측정하면 간단히 시스템에서 사용할 수 있는 전체 메모리 양을 알 수 있게 된다.

문자열 포인터변수를 위한malloc()

18_2.c

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

main()
{
char *imsip;

//imsip = (char *)malloc(10);
if((imsip = (char *)malloc(10)) == NULL) {
puts("Not enough memory to allocate");
exit(0);
}

strcpy(imsip, "archie");
puts(imsip); (1)
free(imsip);
puts(imsip); (2)
}

gcc -o 18_2 18_2.c
./18_2
archie(1)

(2)

반드시 위와 같이 에러처리를 하는 습관을갖도록 하자.

구조체 포인터 변수를 위한malloc()

struct grades {
char *name;
int grade;
} grade;

...

struct grades *imsi;
imsi = (struct grades *)malloc(sizeof(struct grades));

malloc()은grades에 해당하는 구조체크기 만큼의 메모리를 할당한 후그 주소 값을 리턴한다.

이제는 imsi를 이용하여 malloc()에 의해서 할당된 메모리 영역을 이용할 수 있게 된다.

조심할 것은 imsi는 malloc()에 의해서 할당받은 장소 그 자체가 아니라 포인터 변수로써4byte를 차지하고 malloc()에의해서할당된 메모리는 다른 곳에 존재한다.

imsi는 단지 malloc()에 의해서 할당된 메모리 번지를가리키고 있는 것 뿐이다.

그러므로 다음은 다른개념으로 바라 보아야 한다.

struct grades imsi;

struct grades *imsip;

imsi는 구조체명이며 메모리 할당을 받지 않는다.구조체 멤버 name, grade를 참조하기 위한 이름으로써 imsi를 지정한 것이다.

그에 반해 imsip는 구조체 포인터 변수이므로 메모리 할당을 받는다. 포인터 변수이기 때문에4byte를할당받는다.

물론imsi를 가리키도록 할 수도 있으며 용도에 따라서 malloc()을 사용하여 새로운 영역을 만들고imsip가 이를가리키도록 할 수있다.

calloc()

대체하는 함수가 있어서 많이 사용하지느않지만"적어도" 알고는 있어야 하는 함수이다.

malloc()과 calloc의 차이점

calloc()은 malloc()과 마찬가지로 메모리를 할당하는 함수이다.

차이점이라면 할당한 메모리 영역을 초기화 하느냐 하지 않느냐는 점이다.

malloc()은 할당한 메모리 영역을쓰레기값 그대로 보존하며calloc()은 할당한 영역을 0으로 초기화한다.

calloc()이 메모리 영역을 초기화하는데 시간을 소비하는 것은 당연하므로 꼭 필요한 경우가 아니면 malloc()을 사용하는 것이 좋다.

calloc()은하향 추세로 가고 있는 함수 중 하나이다.

calloc()의 원형은 다음과 같다.

void *calloc(size_t count, size_t size);

간단한 예제를 살펴보자.

char *imsip
struct grades *grade;
imsip = (char *)calloc(10,sizeof(char));
grade = (struct grades *)calloc(10, sizeof(struct grades));

calloc()은 malloc()을 이용하여 다음과 같이 작성할 수도 있다.

void*my_malloc(size_t count, size_t size)

{

size_t total_size = count* size;

void *imsi = malloc(total_size);

if(imsi!=NULL)

memset(imsi,0,total_size);

return imsi;

}

calloc() 함수의 구현을 보면 malloc()을 수행한 후에 memset()을 이용한다는 것 이외에는 malloc()과 차리가 없다.

realloc()

반드시 기존에 존재하는영역을 재할당할 때에만 의미가 있다.

새로운 영역을 할당받으려면 당연히 malloc()을 사용하면 된다.

만약,realloc()을 이용하여 새로운 영역을 할당 받고 싶다면 realloc()의 가장 첫번째 인자를 NULL로 지정하면 된다.

realloc()은 할당된 메모리 영역을 줄이거나늘일 수 있지만 일반적으로 늘이기 위해서 사용한다.

만일 늘일 공간이 기존의 공간에 연이어 충분하지 않으면 새로이 확보한 영역에 기존의 정보를 모두 옮기게된다.

이때, 기존의 메모리 영역은 시스템에 반납하기 때문에 이 영역을 다른 지시자가 가리키고 있으면 안된다.

한가지 더 조심해야 할 것은 늘리려는 영역이 존재하지 않으면새로이 전체 영역을 할당해버리게 된다.

그래서 기존의 정보들이 새로운 영역에 옮겨지는 현상이 발생한다. 따라서, 두번째 인자에는"기존 메모리 영역 + 추가 영역의 수치"가 들어간다.

realloc()의 원형과 예제를 살펴보자.

void *realloc(void *ptr, size_t newsize);

struct grades *grade;

grade = (structgrades *)malloc(5 * sizeof(struct grades));

...

grade= (structgrades *)realloc(grade, (5 *sizeof(struct grades)) + sizeof(struct grades));

메모리 해제

더이상 사용하지 않는 메모리 영역을 시스템이 재사용할 수 있도록 해제해야 하는데 이것을 처리해주는 함수가 free()이다.

void free(void *ptr);

리턴 값은 엇으며 단지 해제하려는 포인터만지정해주면된다.

리스트의 메모리 해제

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

struct grades {
char *name;
int grade;
struct grades *next;
};

struct grades *HEAD;

main()
{
struct grades *imsip;

imsip = (struct grades *)malloc(sizeof(struct gradesc));
imsip->name = (char *)malloc(20 + 1);
...
}

리스트의 모든 메모리를 해제하기 위해서는 가장 첫번째 구조체의 주소를 알아야하는데 그 주소를HEAD가 갖고 있다.

그러므로 리스트의 메모리를 해제하려면 HEAD의 주소를인자로 넘기는 간단한 하무하나를 작성하면될 것이다.

void list_free(struct grades *head)
{
while(head != NULL)
{
struct grades *imsi = head->next; // (1)
free(head->name); //(2)
free(head); // (3)
head = imsi;// (4)
}
}

head-> A|· --> B|· --> C|· --> D|· --> E|NULL

head는A 구조체를 가리키고 있으며 head->next는 B를 가리키고 있는데 (1)으로 B 구조체가 저장된 곳의 주소가 imsi에 할당된다.

imsi가B 구조체를 가리키고 있으므로 이제 A 구조체를 해제하면된다.

A 구조체를 가리키고 있는 것은 head 포인터 변수이므로 free(head)를 통해 모든 메모리가 해제된다.

주의할 것은head가 가리키고 있는 구조체 중에서 따로 해제할 멤버를 찾는 것이다.

바로,A구조체에 있는 name이라는 포인터 변수이다.name은 포인터 변수이기 때문에 정확히 4byte 밖에는 메모리를 할당받지 않는다.

만약name 자체를 해제하지 않으면free()는 단지name변수에 대한4byte만 메모리 해제를 할 것이다.

문자열 "jae une"이 저장된 영역은 메모리를 자동으로 할당해주지 않기 대문에 프로그램이 끝날 때까지 이 영역은 사용될 수 없다.

"jae une"에 대한 접근은 A->name으로 밖에 할 수 없는데 A 구조체를 해제하면 "jae une"은 더이상 참조 조차도 할 수 없는 상태가 된다.

즉, 쓰레기가되어 메모리 낭비가 발생하게 된다. 따라서, (2)와 (3)을 통해 A 구조체안에 있는 멤버 중에서 포인터 변수를 먼저 해제하고 A 구조체를 해제해야 한다.

그럼A 구조체의 메모리 해제는 완료가 된다.

그 후에 (4)를 통해 head는 다시 B구조체를 가리키게 되고 앞서 말한 과정을 통해B, C, D 구조체의 메모리 해제가 이루어진다.

'C언어' 카테고리의 다른 글

makefile 만들기  (0) 2011.10.16
Chapter 19. 라이브러리  (0) 2011.10.16
Chapter 15. 함수 인자 포인터  (0) 2011.10.16
Chapter 16. 함수 포인터  (0) 2011.10.16
Chapter 17. 구조체와 포인터  (0) 2011.10.16