C언어

Hagi 2010. 7. 13. 08:31

(출처 : http://dj0155.tistory.com/11 )



프로그램이 메모리에 올라가게 되면 Data Segment 와 Code Segment 로 나뉘게 된다.

 

Heap 과 Stack 은 Data Segment 를 이용하게 된다.

 





  • 힙(Heap)은 런 타임시에 크기가 결정되는 요소들이 저장되는 공간이다.

C의 malloc() 함수나 C++의 new 연산자로 메모리 할당이 될 때에는 이 Heap 공간에 메모리가 잡히게 된다.

 





  • 스택(Stack)은 컴파일시에 크기가 결정되어있는 요소들이 저장되는 공간이다.

함수가 받는 매개 변수나 함수내에서 사용되는 지역변수가 이 Stack 영역에 저장이 된다.

 

 

메모리를 가상으로 그림으로 그려보면 다음과 같이 나타낼 수 있다.

 

 

※데이터 영역

전역 변수와 static 변수 저장

----------------------------------

※힙 영역

동적 할당되는 데이터 저장

 (데이터가 위부터 순차적으로 저장)

 

 

 

※스택 영역

지역 변수와 매개변수가 저장

 (데이터가 아래부터 순차적으로 저장)

 

 

보통 전역 변수나 static 변수는 heap 의 윗 부분에 위치하고, Stack 은 Data Segment 의 처음부터 할당이 되고,

 

Heap 은 끝부분 부터 할당이 된다.

 

 

예제 코드를 작성하여 돌려보았다.

 

 

#include <stdio.h>

int A, B;

main()
{
 int a = 0;
 int b = 0;

 int *p1 = NULL;
 int *p2 = NULL;

 p1 = (int*)malloc(sizeof(A));
 p2 = (int*)malloc(sizeof(A));

 printf("전역 변수의 주소값 출력\n");
 printf("%d\n", &A);
 printf("%d\n", &B);
 printf("동적할당된 포인터의 주소값 출력\n");
 printf("%d\n", p1);
 printf("%d\n", p2);
 printf("지역 변수의 주소값 출력\n");
 printf("%d\n", &a);
 printf("%d\n", &b);

 free(p1);
 free(p2);
}

 

Visual Studio 2003에서 작성하였고, 알아보기 쉽게 10진수로 출력을 하였다.

 

메모리 할당이 된 간격은 틀리지만,

 

전역 변수와 동적 할당된 포인터의 메모리 할당 순서가 지역 변수의 메모리 할당 순서와 다른것을 확인할 수 있다.

 

 

Stack 영역에 올라가는 데이터는 프로그램이 실행되자마자 마로 메모리에 할당이 되고, 함수가 종료되거나, 프로그램이 종료될 때 자동으로 메모리 공간이 해제된다.

 

 

그에 반해, Heap 영역에 올라가는 데이터는 프로그램이 실행되는 중간에 메모리에 할당되고, 프로그램이 종료될 때

 

자동으로 메모리 공간이 해제되지 않는다. 메모리 공간을 해제하면 다시 그 영역을 사용할 수 있다.

 

 

따라서 동적으로 할당된 메모리 공간은 C의 free() 함수나 C++의 delete 연산자를 사용하여 메모리 공간을 다시 해제하여 주어야 한다.

 

그렇지 않을 경우 메모리 누수(leak) 가 발생하게 된다.

 

 

그렇다면 메모리 동적할당이 왜 필요한 것인가?

 

 

예를들어 다음과 같이 5개의 정수형 데이터를 입력하는 프로그램이 있다고 하자.

 

#include <stdio.h>

 

main()
{
 int arr[5];

 int i = 0;
 for(i = 0; i < 5; i++) {
  printf("정수 입력 : ");
  scanf("%d", &arr[i]);
 }
 for(i = 0; i < 5; i++) {
  printf("%d번째 입력 데이터 : %d\n", i+1, arr[i]);
 }
}

 

이 프로그램은 죽었다 깨어나도 5개의 정수형 데이터 밖에 입력을 하지 못한다. 이제 이 프로그램을 실행 시 마다 입력 되는 데이터의

 

수가 다르게 작성을 하고 싶어서 다음과 같이 프로그램을 작성하였다.

 

#include <stdio.h>

void func(int n);

main()
{
 int *p = NULL;
 int i = 0, n = 0;
 printf("입력할 데이터의 수를 입력 : ");
 scanf("%d", &n);
 func(n);
}

void func(int n)
{
 int arr[n];                     // Compile Error
 int i = 0;
 for(i = 0; i < n; i++){
  printf("정수 입력 : ");
  scanf("%d", &arr[i]);
 }
 for(i = 0; i < n; i++)
  printf("%d번째 입력 데이터 : %d\n", i+1, arr[i]);
}

 

-------------------------------------------------------------------------------------------------------------------

func() 함수 내의 arr 이라는 배열은 func 함수가 실행된 후 종료될 때 스택 메모리 영역에서 메모리가 해제 되므로

 

출력까지 func 함수 내에서 처리하였다. 주의깊게 보아야 할 부분은 배열 선언 시 인덱스 값으로 변수가 올 수 없다는 것이다.

 

(배열 선언시의 인덱스 값에는 상수만이 올 수 있다.)

-------------------------------------------------------------------------------------------------------------------

 

그럼 다음과 같은 문제를 포인터를 사용한 동적 메모리 할당으로 해결해보면 다음과 같은 방식으로 작성할 수 있다.

 

포인터와 배열의 차이점과 공통점을 어느정도 알고 있어야 한다.

 

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


main()
{
 int *p = NULL;
 int i = 0, n = 0;
 printf("입력할 데이터의 수를 입력 : ");
 scanf("%d", &n);
 p = (int*)malloc(sizeof(int)*n);
 for(i = 0; i < n; i++){
  printf("정수 입력 : ");
  scanf("%d", (p)+i);
 }
 for(i = 0; i < n; i++)
  printf("%d번 입력 데이터 : %d\n", i+1, *((p)+i));

 free(p);                   // Important!!
}

 

 

-------------------------------------------------------------------------------------------------------------------

main() 함수 내에 포인터 변수 p가 선언되어 있고, 이를 입력받은 n의 갯수만큼 int 형으로 동적 메모리 할당을 하고 있다.

이후 포인터 변수 p를 이용하여 데이터를 입력받고 출력 한 후 할당받은 메모리 공간을 해제한다.

-------------------------------------------------------------------------------------------------------------------

 

 

동적 메모리 할당을 사용하면 위와 같은 이점을 활용할 수 있게된다.

 

 

C++에서의 new 와 delete 연산자를 이용한 메모리 할당/해제 방식은

 

 

int *p;

p = new int[데이터갯수];

delete[] p;

 

와 같은 방법으로 할당과 해제를 한다.

 

예를들어 '데이터 갯수' 부분에 5를 입력하게 되면

 

malloc(sizeof(int)*5) 와 같은 크기를 할당하게 된다. (new 연산자 사용시에는 데이터 타입을 알 수 있다.)