1. 정적할당과 동적할당
우리가 프로그래밍을 할때 변수를 할당하는 메모리 영역은 크게 스택(stack) 영역과 힙(heap) 영역으로 나뉘어집니다. 스택 영역은 메모리 영역중 매우 적은 영역만을 할당받고 있으며, 나머지 대부분의 영역이 힙 영역입니다. 데이터 구조상 힙 영역이 더 큰 공간을 사용하기에 합리적이기 때문이죠. (이것에 대한 지식은 자료구조 관련 서적을 보시기 바랍니다.) C++ 코드를 예로 들어가며 설명해드리겠습니다.
| int a = 73; char string[100]; strcpy(string, "abc"); |
| 코드1. 정적할당의 예 |
위와 같이 일반적인 변수선언은 스택영역에 메모리를 할당해줍니다. a라는 변수는 정수형이므로 스택영역에 4바이트를, string이라는 변수는 문자형 배열 100칸이므로 1바이트*100인 100바이트를 할당해줍니다.
스택영역은 메모리 할당과 해제가 자동이므로 저렇게 선언해주시고 사용만 하시면 알아서 메모리에서 해제가 됩니다. 스택영역에는 한 번 할당하면 끝까지 할당한 형태로만 사용해야하기 때문에 정적 할당(static allocation)이라고 부르죠.
스택영역과는 달리 힙영역에 할당하는 방식을 동적 할당(dynamic allocation)이라 부릅니다. 힙영역에 메모리를 할당하는 방식은 스택영역에 포인터 변수를 할당하고 그 포인터를 사용해서 힙 영역의 임의의 공간을 가리켜서 그 가리킨 곳부터 원하는 크기만큼을 할당해 사용하는데, 이 크기를 해제하고 또 다른 크기로 재할당하는 등 변화를 줄 수 있기 때문에 동적 할당이라 부르는 것이죠. 다음 그림을 보시기 바랍니다.
![]()
![]()
![]()
동적할당받은 공간은 다른 프로그램으로부터 보호를 받습니다. 힙 영역은 모든 프로그램이 공유하는 영역이기 때문이죠. 그렇기 때문에 보호할 필요가 있는 겁니다. 때문에 동적할당받아서 사용을 한 후, 사용이 끝났으면 이 영역을 해제해주어야 합니다. 그렇지 않으면 프로그램이 종료된 이후에도 보호된 상태로 남아있기 때문에 시스템이 이상한 현상을 보일 수도 있습니다. 다음은 정적할당하는 방법과 해제하는 방법입니다.
| int* a = (int*)malloc( sizeof(int) ); char* string = (char*)malloc( sizeof(char) * 100 ); strcpy(string, "abc"); free( a ); free( string ); |
| 코드2. 동적할당의 예1 ~ C 컴파일러의 경우 |
| int* a = new int; char* string = new char[100]; strcpy(string, "abc"); delete a; delete [] string; |
| 코드3. 동적할당의 예2 ~ C++ 컴파일러의 경우 |
C++ 컴파일러는 기본적으로 동적할당/해제 연산자인 new와 delete를 제공하지만 C에서는 그렇지 않기 때문에 malloc()과 free()라는 함수를 사용해야 합니다. 하지만 요즘은 C 전용 컴파일러만 있는 경우는 거의 드물기 때문에 C++ 컴파일러를 기준으로 설명하겠습니다. 일반 변수와 1차원 배열을 동적할당하는 방법을 위에서 보셨습니다. 그렇다면 2차원배열은 어떤식으로 할까요? 아래와 같을까요?
2. 2차원 배열의 동적할당
| char* text = new char[100][50]; |
| 코드4. 2차원 배열의 동적할당? |
위와 같이 선언하시고 컴파일해보시기 바랍니다. 저는 마이크로소프트사의 C++ 컴파일러 v7.1을 사용하는데, 제 컴파일러의 경우에는 아래와 같은 컴파일 에러 메시지를 띄웁니다.
| error C2440: '초기화 중' : 'char(*)[50]'에서 'char *'(으)로 변환할 수 없습니다. |
| 표1. 컴파일 에러 메시지 |
C++의 배정 연산자 '='은 좌측변수의 타입과 우측 값의 타입이 동일하거나 변환이 가능할 때 실행됩니다. 코드4의 경우에는 포인터 배열을 포인터 변수에 대입하려했기 때문에 에러가 난 것입니다.
포인터 배열이란 포인터 변수들로 이루어진 배열을 말합니다. 즉, 배열의 값으로 주소가 들어가는 배열을 말하죠. 포인터 변수는 말 그대로 배열이 아닌 변수 하나입니다. 주소를 값으로 가지고 있는 하나의 변수지요. 코드4를 살펴보면 좌측값(l-value)에는 포인터 변수가 자리잡고 있습니다(문자 포인터형). 그런데 우측값(r-value)에는 2차원 배열이 있군요. 배열의 변수명은 포인터 변수처럼 사용할 수 있습니다. 즉 int a[50]을 선언했다면 a라는 배열이름은 포인터 변수처럼 쓸 수 있는 것이죠. 왜냐면 배열의 이름은 배열의 첫번째 인덱스의 주소값을 의미하기 때문입니다.
그렇게 생각해보면 코드4의 char[100][50]은 표1의 에러메시지처럼 char(*)[50]으로 바꿀 수 있습니다. 그래서 2차원 배열은 포인터 배열에 대입될 수 있는 것입니다. 그럼 코드4를 에러가 안나도록 고쳐보죠.
| char(* text)[50] = new char[100][50]; |
| 코드5. 2차원 배열의 동적할당 수정 |
코드5처럼 고치면 정상적으로 할당이 되며 text는 완벽히 2차원 배열처럼 동작합니다. text가 메모리에 할당된 모습을 보도록 하죠. 참고로 각 배열값에 들어가 있는 \0은 text를 NULL로 초기화했다는 가정아래 적은 값입니다.
![]()
그림4에서 보이는 것처럼 text 배열은 2차원 배열처럼 작동하지만 메모리 구조를 살펴보면 여전히 스택영역에 4바이트 * 50칸 = 200바이트를 차지하고 있습니다. 그다지 큰 값은 아니지만 스택영역의 일부를 차지하고 있다는 것을 보면 깔끔한 동적할당이라 볼 수 없습니다. 그럼 어떻게 동적할당을 해야 깔끔하게 가능할까요? 이를 그림으로 한 번 보겠습니다.
![]()
그림5처럼 할당을 한다면 스택영역에는 단 하나의 포인터인 4바이트만 소비하고 나머지는 모조리 힙영역에 할당되므로 이것이야말로 동적할당의 참모습이라 할 수 있습니다. 이렇게 할당하기 위해서는 text가 주소의 주소를 가리켜야 하므로 이중 포인터(doubly pointer)로 만들어야 합니다. 그래야 text의 값 또한 주소를 가질 수 있기 때문입니다.
| char** text; |
| 코드6. 이중 포인터의 선언 |
코드6처럼 이중 포인터는 포인터 연산자 *를 두 번 씀으로써 사용이 가능합니다. 삼중 포인터라면 *를 세 개 붙이면 되겠죠. 이제 여기에 동적할당을 할 차례입니다. 동적할당은 그림5를 보면서 설명드리자면, 가운데 있는 50칸짜리 공간을 할당한 후, 반복문을 이용해서 50칸에 각각 100칸짜리 공간을 할당하는 방식을 씁니다. 코드6은 50칸짜리 공간을 할당하는 코드입니다.
| char** text = new char*[50]; |
| 코드6. 이중 포인터를 이용한 배열의 동적할당 #1 |
코드6을 보면 text가 이중 포인터이기 때문에 new 연산자 오른쪽에 위치한 코드가 char[50]이 아닌 char*[50]으로 써있습니다. 이 부분에 주의하셔야 합니다. 그럼 50칸을 생성했으니 100칸을 생성해봅시다.
| char** text = new char*[50]; for(int i = 0; i < 50; i++) text[i] = new char[100]; |
| 코드7. 이중 포인터를 이용한 배열의 동적할당 #2 |
코드7의 text[i]에 해당하는 그림이 바로 그림5의 가운데 있는 칸들입니다. 이 50개의 칸에 for문을 50번 돌리면서 100칸짜리 방(그림5의 오른쪽)을 만드는 것이죠. 이렇게 할당이 끝났으면 text는 또 다시 완벽한 2차원 배열처럼 사용이 가능합니다. 참고로 for문 내의 char[100]부분의 100이라는 숫자를 i에 따라 다르게 쓰시면 각 배열의 크기가 다른 2차원 배열도 만들 수 있습니다.
할당을 했으니 다 사용하신 후에 해제하는 방법도 아셔야죠.^^ 해제의 순서는 할당과 반대로 오른쪽에 있는 100칸짜리들부터 해제합니다. 50칸을 먼저 해제해버리면 100칸짜리들의 주소가 어딘지 알 수 없게 되버리니까요.
| for(i = 0; i < 50; i++) delete [] text[i]; delete [] text; |
| 코드8. 이중 포인터를 이용한 배열의 해제 |
3. 고차원 배열의 동적할당
설명이 상당히 어설픈것 같았는데 이해가 가셨는지 모르겠네요-_-; 그럼 2차원을 확장해서 3차원 배열의 동적할당을 해봅시다. 방법은 2차원과 같습니다. 단지 반복문이 하나 더 늘어나고 포인터가 삼중이 되는 것일 뿐이죠. ^^ text[10][50][100]을 만드는 것으로 간단히 그림과 코드를 살펴보겠습니다.
![]()
| // 할당 char*** text = new char**[10]; for(int i = 0; i < 10; i++) { text[i] = new char*[50]; for(int j = 0; j < 50; j++) text[i][j] = new char[100]; } // 해제 for(i = 0; i < 10; i++) { for(int j = 0; j < 50; j++) delete [] text[i][j]; delete [] text[i]; } delete [] text; |
| 코드9. 삼중 포인터를 이용한 배열의 동적할당 및 해제 |
4차원 배열은 쓸 일이 거의 없겠지만, 마찬가지 방법이겠죠? :)
이상 고차원 배열의 동적할당이었습니다^^
'C/C++' 카테고리의 다른 글
| [C언어] 알고리즘 문제(3n+1) (0) | 2008/01/10 |
|---|---|
| [C++] BOOL과 bool 차이 (4) | 2007/11/06 |
| [C&C++] new & malloc 메모리 할당 (0) | 2007/11/06 |
| [C언어] 지뢰찾기 (0) | 2007/08/16 |
| [C언어] 구구단 (0) | 2007/08/16 |
| [C언어] 개미 수열 (0) | 2007/08/16 |


