C 메모리 구조와 동적 할당

프로그래밍/C 2019.05.25 댓글 Plorence

C언어 메모리 구조

메모리 구조는 4개의 영역으로 나뉘어 있습니다.

메모리 공간을 나눠서 유사한 성향의 데이터를 묶어서 저장을하면 관리가 용이해지고 메모리의 접근 속도가 향상됩니다.

 

각 영역

코드 영역(Code Area)

코드 영역은 이름 그대로 실행할 프로그램의 코드가 저장되는 메모리 공간입니다.

따라서 CPU는 코드영역에 저장된 명령문들을 하나씩 가져가서 실행을 합니다.

데이터 영역(Data Area)

데이터 영역에는 전역변수와 static으로 선언되는 static변수가 할당됩니다.

즉, 이 영역에 할당되는 변수들은 프로그램의 시작과 동시에 메모리 공간에 할당되어 프로그램 종료 시까지 남아있게 된다는 특징이 있습니다.

스택 영역(Stack Area)

스택 영역에는 지역변수와 매개변수가 할당됩니다.

이렇듯 이 영역에 할당되는 변수들은 선언된 함수를 빠져나가면 소멸된다는 특징이 있습니다.

힙 영역(Heap Area)

데이터 영역에 할당되는 변수와 스택 영역에 할당되는 변수들은 생성과 소멸이 시점이 이미 결정되어 있습니다.

프로그램을 구현하다 보면 두 영역과는 다른 성격의 변수가 필요하기도 합니다.

그래서 C언어에서는 힙 영역이라는게 있습니다.

 

변수가 메모리 영역에 할당되는 순서

  1. static변수, 전역 변수가 데이터 영역(Data Area)에 할당됨

  2. 함수가 호출되면 매개변수와 지역변수가 스택 영역(Stack Area)에 할당됨.

  3. return을 만나면(return은 함수의 종료) 스택 영역에서 소멸된다.

 

지역 변수와 같이 함수가 호출될 때마다 매번 할당이 이뤄지지만, 할당이 되면 전역 변수와 마찬가지로 함수를 빠져나가도 소멸되지 않은 성격의 변수가 필요합니다.

생성과 소멸의 시기가 지역변수나 전역변수와 다른 유형의 변수는 malloc와 free라는 이름의 함수를 통해서 힙 영역(Heap Area)에 할당되고 소멸할 수 있습니다.

 

힙 영역의 메모리 공간 할당과 해제

동적 할당 관련해서는 5개의 함수가 있습니다.

void* malloc( size_t size ); //힙 영역으로의 메모리 공간 할당
void free(void * ptr); //힙 영역에 할당된 메모리 공간 해제
void* calloc( size_t num, size_t size ); //힙 영역으로의 메모리 공간 할당
void *realloc( void *ptr, size_t new_size ); //힙 영역에 할당된 메모리 공간을 재 할당
void *aligned_alloc( size_t alignment, size_t size ); (since C11) //https://en.cppreference.com/w/c/memory/aligned_alloc

malloc 함수

malloc함수의 호출을 통한 메모리 공간의 할당을 가리켜 '동적 할당(dynamic allocation)'이라 합니다.

이유는 할당되는 메모리의 크기를 프로그램의 실행 중간에 호출되는 malloc 함수가 결정하기 때문입니다.

이러한 동적 할당의 함수는 런타임 때 사이즈를 결정할 수 있습니다.

크기를 알 수 없다면 컴파일 타임때 배열의 크기를 엄청 크게 정의해두고 하는 것보단 메모리 측면에서 효율적입니다.

그리고 할당된 메모리 공간은 쓰레기값입니다.

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main(void) {
       int * ptr = malloc(sizeof(int) * 10); //길이가 10인 int형 배열(40바이트만큼  할당)
       ptr[1] = 100;
       ptr[5] = 200;
       printf("%d %d", ptr[1], ptr[5]);
}

사용의 예시입니다.

할당에 실패하게 되면 Null pointer(0)을 반환합니다.

 

malloc함수의 반환형이 void형 포인터인 이유

void형 포인터는 주소 값만 가지고 있고, 이 주소 값에 접근을 할 수 없습니다.

왜 반환형이 void형 포인터냐면 포인터 형을 결정할 수 없기 때문입니다.

4를 전달하면 int형 변수로 사용할지 float형 변수로 사용할지, 아니면 길이가 4인 char형 배열로 사용할지 모르기 때문입니다.

 

malloc함수의 형 변환에 관련해서

https://untitle-ssu.tistory.com/69

요약:실수를 줄이기 위해선 명시적 형 변환을 해주는 게 좋다.

 

free 함수

free함수는 동적 할당과  반대되는 의미를 가지고 있습니다.

malloc함수를 통해 할당한 메모리 공간을 해제하는 역할을 합니다.

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main(void) {
       int * ptr = malloc(sizeof(int) * 10); //길이가 10인 int형 배열(40바이트만큼  할당)
       free(ptr);
}

free함수를 통해 할당 해제를 안 하더라도 어차피 프로그램 종료하면 메모리 공간에 할당된 것들은 모두 소멸됩니다.

하지만 실제 프로그램 구현에서는 반드시 free함수를 호출하는 것이 좋습니다.

왜냐하면 사이즈가 크고 잦은 할당을 반복하다 보면 메모리 누수가 발생하게 됩니다.

그러므로 malloc함수의 호출 횟수만큼 free함수를 호출하는 습관을 들이는 게 좋습니다.

 

calloc 함수

malloc함수와 유일한 차이점은 메모리 공간의 할당을 위한 인자의 전달 방식입니다.

첫 번째 전달 인자로는 블록의 개수, 두 번째 전달 인자로는 블록 하나당 바이트 크기입니다.

malloc함수는 "총 40 바이트를 힙 영역에 할당해 주세요."

calloc함수는 "4바이트 크기의 블록(elt_size) 10개를(elt_count) 힙 영역에 할당해 주세요."

 

그리고 malloc함수는 할당된 메모리 공간을 초기화하지 않고(쓰레기 값), calloc함수는 0으로 초기화합니다.

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main(void) {
       int * ptr = calloc(sizeof(int), 10);
       ptr[1] = 100;
       ptr[5] = 200;
       for (int i = 0; i < 10; i++) {
              printf("%d \n", ptr[i]);
       }
       free(ptr);
}

출력 결과

0
100
0
0
0
200
0
0
0
0

realloc 함수

메모리 공간 확장은 힙 영역에서만 가능합니다.

relloac 함수는 이미 할당되어 있는 메모리 공간에서 새로운 크기로 재할당 될 때 쓰입니다.

실패 시 null pointer(0)을 반환합니다.

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main(void) {
       int * ptr = malloc(sizeof(int)*6);
       ptr[1] = 100;
       ptr[5] = 200;
       printf("p:%p \n", ptr);
       ptr = (int*)realloc(ptr,sizeof(int)*10);
       printf("p:%p \n", ptr);
       for (int i = 0; i < 10; i++) {
              printf("%d \n", ptr[i]);
       }
       free(ptr);
}

반환 값을 기준으로 두 가지로 구분이 됩니다.

  • malloc 함수의 반환 값과 realloc함수의 반환 값이 같을 경우

  • malloc 함수의 반환 값과 realloc함수의 반환 값이 다를 경우

 

즉 초기에 할당된 메모리 주소 값과 재할당 했을 때 메모리 주소 값을 말합니다.

같은 경우에는 기존에 할당된 메모리 공간의 뒤를 이어서, 확장할 영역이 넉넉한 경우에 발생합니다.

넉넉하지 않은 경우 힙의 다른 위치에 할당하기 때문에 반환 값이 다를 수 있습니다.

https://www.pmguda.com/61 에 따르면 반환값이 다른 경우(새로운 영역에 할당됐을 경우)에도 값을 잃어버릴 일은 없다고 합니다.

동적 할당에 대해 어느 정도 이해를 했고, relloac에 대해 깊게 이해하고 싶으면 위 링크를 정독하시는 게 매우 도움이 될 겁니다.

 

댓글