동적할당과 메모리구조

동적할당/정적할당이란

우리가 변수를 만들면 메모리에 그 만큼의 영역이 할당된다는것은 다들 알것이다.
동적할당과 정적할당은 그 할당의 방법을 말하는것이다.

우리가 여태껏 알아왔던 다음과 같은 변수의 선언방식은,

#include <stdio.h>

int main(void){
    int a;
    return 0;
}
정적할당으로서 메모리의 크기가 프로그램이 시작하기전에 결정이 된다.

따라서 정적할당된 배열의 경우에는 다음과 같은 특징을 가진다.

- 사용할 만큼 배열의 크기를 지정하지 못하기 때문에 낭비되는 공간이 생길 확률이 크다.
- 프로그램이 종료될 때에만 메모리가 해제된다.

그러나 동적할당은 프로그램이 실행된 뒤 동적으로 메모리가 할당되는 방식으로, 다음과 같은 특징을 가진다.

- 프로그램이 종료되기 전, 할당된 메모리를 해제시킬 수 있다.
- 사용할 만큼의 공간을 할당함으로서, 낭비되는 공간이 생길 확률이 적다.

동적할당을 사용하는 이유

위에서도 말했듯, 동적할당은 메모리를 동적으로 할당해야 할 일이 생길때 사용하는것이다.
예를들어, 
#include <stdio.h>

int main(void){
    int a=100;
    int arr[a];
    return 0;
}
이러한 코드는 컴파일시에 오류를 낼것이다. 그러나 동적할당을 통한다면 이와 동일한 작업을하는 코드를 짤 수 있다.

동적할당 사용법

동적할당을 하기 위해서는 알아야 할 함수가 총 4개가 있는데, 그것들은 다음과 같다.
malloc: 메모리에 인자값으로 넘겨진 정수 만큼의 공간을 할당함
calloc: 메모리에 인자값으로 넘겨진 정수 만큼의 공간을 할당하고, 초기화화
realloc: 이미 할당된 변수의 크기를 변경할 때 사용한다.
free: 동적할당된 변수를 해제한다.

malloc 예제
#include <stdio.h>
#include <stdlib.h>

int main(void){
 int *test = (int*)malloc(sizeof(int));
 *test = 3;
 printf("test: %d", *test);
 free(test);
 return 0;
}
malloc 함수는 반환하는 값의 자료형이 void 형 포인터이기 때문에, 사용하고자 하는 타입으로 형 변환을 해 주어야한다.

calloc 함수도 마찬가지로 반환하는 값의 자료형이 void 형 포인터이다.
#include <stdio.h>
#include <stdlib.h>

int main(void){
 int *test = (int*)calloc(1, sizeof(int));
 *test = 3;
 printf("test: %d", *test);
 free(test);
 return 0;
}
calloc은 할당할 개수, 각 개수의 크기를 인자값으로 넘기는것으로 사용 할 수 있다.
#include <stdio.h>
#include <stdlib.h>

int main(void){
 int i;
 int *test = (int*)calloc(2, sizeof(int));
 test[0] = 3;
 test[1] = 4;

 realloc(test, sizeof(int)*4);
 test[2] = 5;
 test[3] = 6;
 for(i = 0;i<4;i++){
  printf("%d\n", test[i]);
 }
 free(test);
 return 0;
}
realloc은 크기를 변경하고자 하는 변수와 크기를 차례대로 인자값으로 넣어주면 된다.
저 코드의 실행결과에 따르면, 인덱스 순서대로 3, 4, 5, 6이 출력되기 때문에 메모리 크기를 확장할 경우 기존 메모리주소는 변경되지 않은 채로 변수의 크기가 확장된다는것을 알 수 있다.

주의할 점

메모리 누수라는것을 들어본 적이 있는가?
할당해 둔 메모리가 해제되지 않은채 유지되어 메모리를 필요 이상으로 낭비하게 되는 경우를 말한다.
이러한 경우는 할당한 메모리의 주소를 잃어버렸거나, 프로그래머의 실수로 해제하는것을 까먹었을때 발생한다.
또한 이중해제를하게되면 오류가 날 수 있으니 조심하도록 하고, 마지막으로 댕글링 포인터에 대해 다루겠다.
댕글링포인터란 간단히 말하여 해제된 메모리 공간을 가르키고 있는 포인터이다.
그러니까, 김연규라는 사람이 한강공원에 텐트를 쳐놓고 자기 주소라고 주민등록을 해 뒀는데, 그 텐트를 철거한 상태라는것이다.
만약 다른 사람이 내 자리에 텐트를 쳐두고 김연규라고 주장한다면? 보안적인 문제가 발생할 수 있을것이다.
그래서 해제된 공간을 가리키던 포인터의 값을 Null로 설정하는 방법이 있다.

따라서 동적할당을 사용할때는 주의하여 사용해야 한다는 점을 명심하자.

메모리구조

이렇게 동적할당을 하면, 메모리 중 힙 영역에 할당되게 된다. 그래서 메모리 구조도 같이 공부해볼까 한다.

운영체제가 할당하는 메모리의 영역은 크게 다음과 같이 나눠 볼 수 있다.
  1. 코드(Code) 영역: static
  2. 데이터(Data) 영역: static
  3. 힙(Heap) 영역: dynamic
  4. 스택(Stack) 영역: dynamic
메모리에도 주소가 있는데, 위의 내용은 낮은 주소부터 높은 주소까지 순서대로 나열한 결과이다.

본격적인 설명에 들어가기에 앞서 두 가지의 카테고리를 알아두어야 할 필요가 있다. 바로 static과 dynamic이다.static(정적)으로 분류되는 영역은 당신이 코드를 컴파일하면 그 시간에 정적으로 할당되는 영역이다. dynamic(동적)으로 분류되는 영역은 프로그램이 실행 중에 동적으로 할당되는 영역이다.


1. 코드 영역
텍스트(Text) 영역이라고도 불리는 이 영역은 실행할 프로그램의 코드가 저장되는 공간으로서, 만약 당신이 작성한 C 코드를 실행한다면 이곳에 컴파일러가 기계어로 번역해 준 당신의 코드가 있을 것이다. CPU는 이 영역에 있는 코드(명령어)를 하나씩 가져가서 처리하게 된다 Read-Only(읽음 전용) 변수 또한 저장된다.


2. 데이터 영역
영역 이름 그대로, 데이터가 저장되는 영역이다. 여기서 말하는 데이터란 전역변수, 정적변수, 배열, 구조체 등이 있겠다. 프로그램이 실행될 때 생성되며, 종료 시 시스템에 반환된다. 함수 안에 선언된 static(정적) 변수는 프로그램이 실행될 때 공간만 할당되며, 함수가 실행될 때 초기화된다. (여기에서 static은 코드 내의 정적 변수를 말하며, 메모리 위에서 말한 메모리 카테고리와는 관련이 없다)
그리고 초기화가 되지 않은 변수는 block stated symbol, bss 라고 부르는 공간에 따로 저장이 된다고 한다.

3. 힙 영역
메모리 주소 값에 의해서만 참조되는 영역으로서, 코드 실행 중 필요 때문에 메모리를 추가로 할당하고자 할 때 사용된다. 흔히 말하는 ‘메모리 누수’가 이 힙 영역을 관리해 주지 않아서 일어나며, 우리가 위에서 했던 동적 할당시에 사용되는 공간이다.


4. 스택 영역
프로그램이 자동으로 사용하는 임시 메모리 영역이다.함수의 인자값, 지역변수, 반환(return) 값 등이 저장되며, 스택의 크기는 프로그램마다 할당되지만, 프로그램이 로드될 때 크기가 고정되어서 실행되고 있는 도중에 크기의 변경이 불가능하다.이 공간에서는 push 동작으로 데이터를 저장하고 pop 동작으로 데이터를 꺼내오는데 비유하자면 다음과 같다. 당신이 설거지하고 있고, 설거지가 끝난 그릇을 한곳에 쌓는다고 가정해보자. 이때, ‘그릇을 쌓는 행위’는 push 동작이고, ‘그릇을 꺼내는 행위’는 pop 동작이다. 그렇다 보니, pop을 하게 되면 가장 최근에 push 한 데이터를 받을 것이다.위의 push/pop과 같은 명령을 실행해야 하다 보니 자동으로 증가 / 감소 하므로 보통 메모리의 마지막 번지에 지정하곤 한다.

Comments

Popular Posts