C 언어의 메모리 구조
프로그램을 실행하면 해당 프로그램의 실행을 위한 메모리 공간이 운영체제에 의해서 미리 마련
그리고 바로 이 메모리 공간 내에서 변수가 선언되고, 문자열이 선언되는 것
메모리의 구성
프로그램 실행 시 운영체제에 의해서 마련되는 메모리의 구조는 네 개의 영역으로 구분
코드 영역 / 데이터 영역 / 힙 영역 / 스택 영역
메모리 공간을 나눠놓은 이유는
유사한 성향의 데이터를 묶어서 저장을 하면, 관리가 용이해지고 메모리의 접근속도가 향상
메모리 영역별로 저장되는 데이터 유형
코드 영역
실행할 프로그램의 코드가 저장되는 메모리 공간
따라서, CPU는 코드 영역에 저장된 명령문들을 하나씩 가져가서 실행
데이터 영역
전역변수와 static으로 선언되는 static 변수가 할당
즉, 이 영역에 할당되는 변수들은 프로그램의 시작과 동시에 메모리 공간에 할당되어 프로그램 종료 시까지 남아있게 된다는 특징
스택 영역
지역변수와 매개변수가 할당
이 영역에 할당되는 변수들은 선언된 함수를 빠져나가면 소멸된다는 특징
힙 영역
데이터 영역에 할당되는 변수와 스택 영역에 할당되는 변수들은 생성과 소멸의 시점이 이미 결정되어 있다.
그러나 프로그램을 구현하다 보면, 이 두 영역의 변수들과는 다른 성격의 변수가 필요하기도 하다.
그래서 C 언어에서는 프로그래머가 원하는 시점에 변수를 할당하고 또 소멸하도록 지원을 하는데,
바로 이러한 유형의 변수들이 할당되는 영역이 힙 영역
프로그램의 실행에 따른 메모리의 상태 변화
/* 프로그램 시작 */
int sum=25; 1.전역변수의 할당
-> sum=25 데이터 영역
int main(void)
{
int num1=10; // 실행 위치1
fct(num1); // 호출된 함수
num1++; // 실행 위치3
fct(num1); // 호출된 함수
return 0;
} 4.지역변수 -> num2=12 - 실행 위치4> num2=12 --- 실행 위치3에서 소멸
3.매개변수 -> n=10 -실행 위치4> n= 11 --- 실행 위치3에서 소멸
void fct(int n) 2.지역변수의 할당
{ -> num1=10 -실행 위치3> num1=11 스택 영역
int num2=12; // 실행 위치2, 실행 위치 4
. . . .
}
/*
main 함수가 호출되기 이전에 데이터 영역이 먼저 초기화
위에서 보이듯이 전역변수가, 그리고 위에는 없지만 static 변수가 먼저 데이터 영역에 할당이 되고 나서 main 함수 호출
이어서 main 함수 내에 선언된 지역변수 num1이 스택에 할당
다음으로 fct 함수가 호출되어 매개변수와 지역변수가 스택에 할당
fct 함수가 반환을 하면서 함수 호출 시 할당되었던 매개변수와 지역변수가 소멸
. . . .
main 함수의 return 문이 실행되면서 프로그램 종료
종료 시 운영체제에 의해서 할당된 메모리 공간 전체를 반환 - 이 떄가 전역변수가 소멸되는 시점
*/
메모리의 동적 할당
전역변수와 지역변수로 해결이 되지 않는 상황
/* 프로그램 사용자로부터 입력 받은 문자열의 정보를 반환하는 함수 */
#include <stdio.h>
char * ReadUserName(void)
{
char name[30];
printf("What's your name? ");
gets(name);
return name; // 무엇을 반환하는가?
}
int main(void)
{
char * name1;
char * name2;
name1=ReadUserName();
printf("name1: %s\n", name1);
name2=ReadUserName();
printf("name2: %s\n", name2);
return 0;
}
/*
함수 내에 지역적으로 선언된 배열(변수)의 주소 값을 반환하는 데 문제점이 있음.
함수 내에서 프로그램 사용자로부터 문자열을 입력 받아서 그 결과를 반환하려는데,
문자열이 저장되어 있는 배열(char name[30])이 지역적으로 선언되었기 때문에 함수를 빠져나오면서 소멸된다는 문제
*/
#include <stdio.h>
char name[30];
char * ReadUserName(void)
{
printf("What's your name?: ");
gets(name);
return name;
}
int main(void)
{
char * name1;
char * name2;
name1=ReadUserName();
printf("name1: %s\n", name1);
name2=ReadUserName();
printf("name2: %s\n", name2);
printf("name1: %s\n", name1);
printf("name2: %s\n", name2);
return 0;
}
/* 결과
What's your name?: Yoon sung woo
name1: Yoon sung woo
What's your name?: Choi jun kyung
name2: Choi jun kyung
name1: Choi jun kyung
name2: Choi jun kyung
실행 결과에서 보이듯이 하나의 전역변수를 이용하면, 전역변수를 덮어쓰게 되기 때문에, 함수호출을 통해서 얻게 된 이름정보가 유지되지 않는다.
*/
그렇다면 어떤 성격의 변수가 필요한 것일까?
함수가 매번 호출될 때마다 새롭게 할당되고 또 함수를 빠져나가도 유지가 되는 유형의 변수
생성과 소멸의 시기가 지역변수나 전역변수와 다른 유형의 변수는 malloc과 free라는 이름의 함수를 통해서 힙 영역에 할당하고 소멸할 수 있음.
힙 영역의 메모리 공간 할당과 해제: malloc 과 free 함수
/* malloc 함수를 이용해서 메모리 공간을 할당하고, 할당된 메모리 공간은 free 함수의 호출을 통해서 해제 */
#include <stdlib.h>
void * malloc(size_t size); // 힙 영역으로의 메모리 공간 할당
void free(void * ptr); // 힙 영역에 할당된 메모리 공간 해제
// malloc 함수는 성공 시 할당된 메모리의 주소 값, 실패 시 NULL 반환
/*
힙 영역을 흔히 '프로그래머가 관리하는 메모리 공간'이라고 한다.
이유는 malloc 함수호출로 할당된 메모리 공간은 프로그래머가 직접 free 함수의 호출을 통해서 해제하지 않으면 계속 남아있기 때문이다.
즉, 위의 두 함수는 다음과 같이 쌍을 이루어 호출하게 된다.
*/
int main(void)
{
void * ptr1 = malloc(4); // 4 바이트가 힙 영역에 할당
void * ptr2 = malloc(12); // 12 바이트가 힙 영역에 할당
. . . .
free(ptr1); // ptr1이 가리키는 4 바이트 메모리 공간 해제
free(ptr2); // ptr2가 가리키는 12 바이트 메모리 공간 해제
. . . .
}
/*
위의 코드를 실행하게 되면 ptr1은 첫 번째 malloc 함수호출을 통해서 할당된 메모리 공간의 첫 번째 바이트를 가리킴
malloc 함수는 주소 값을 반환한다. 그리고 그 주소 값을 이용해서 힙에 접근을 해야 한다.
따라서, 포인터를 이용해서 메모리 공간에 접근하는 수 밖에 없다.
*/
malloc 함수의 반환형이 void 형 포인터인 이유와 힙 영역으로의 접근
/* malloc 함수의 반환 값에 아무런 가공도 가하지 않으면, 이를 이용해서는 할당된 메모리 공간에 접근이 불가능 */
void * ptr = malloc(sizeof(int)); // int형 변수 크기의 메모리 공간 할당
*ptr=20; // ptr이 void형 포인터이므로 컴파일 에러
/* 만약 다음과 같이 입력을 하면 */
void * ptr1 = malloc(sizeof(int)); // int형 변수 크기의 메모리 공간 할당
void * ptr2 = malloc(sizeof(double)); // doubl형 변수 크기의 메모리 공간 할당
void * ptr3 = malloc(sizeof(int)*7); // 길이가 7인 int형 배열로 사용할 공간 마련
void * ptr4 = malloc(sizeof(double)*9); // 길이가 9인 double형 배열로 사용할 공간 마련
/* 하지만 sizeof 연산과 곱셈연산 이후에 정작 malloc 함수에게 전달되는 인자는 다음과 같다. */
void * ptr1 = malloc(4);
void * ptr2 = malloc(8);
void * ptr3 = malloc(28);
void * ptr4 = malloc(72);
/* 따라서 다음과 같이 void형으로 반환되는 주소 값을 적절히 형 변환해서 메모리 공간에 접근 */
int * ptr1 = (int *)malloc(sizeof(int));
double * ptr2 = (double *)malloc(sizeof(double));
int * ptr3 = (int *)malloc(sizeof(int)*7);
double * ptr4 = (double *)malloc(sizeof(double)*9);
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int * ptr1 = (int *)malloc(sizeof(int));
int * ptr2 = (int *)malloc(sizeof(int)*7);
int i;
*ptr1 = 20;
for(i=0 ; i<7 ; i++)
ptr2[i]=i+1;
printf("%d \n", *ptr1);
for(i=0 ; i<7 ; i++)
printf("%d ", ptr2[i]);
printf("\n");
free(ptr1);
free(ptr2);
return 0;
}
/* 결과
20
1 2 3 4 5 6 7
*/
위에서 실패한 문자열 함수의 해결
#include <stdio.h>
#include <stdlib.h>
char * ReadUserName(void)
{
char * name = (char *)malloc(sizeof(char)*30);
printf("What's your name? ");
gets(name);
return name;
}
int main(void)
{
char * name1;
char * name2;
name1 = ReadUserName();
printf("name1: %s \n", name1);
name2 = ReadUserName();
printf("name2: %s \n", name2);
printf("again name1: %s \n", name1);
printf("again name2: %s \n", name2);
free(name1);
free(name2);
return 0;
}
/* 결과
What's your name? Yoon Sung Woo
name1: Yoon Sung Woo
What's your name? Hong Sook Jin
name2: Hong Sook Jin
again name1: Yoon Sung Woo
again name2: Hong Sook Jin
*/
malloc 함수의 사촌 뻘 되는 calloc 함수
/* malloc 함수의 유일한 차이점은 메모리 공간의 할당을 위한 인자의 전달 방식 */
#include <stdlib.h>
void * calloc(size_t elt_count, size_t elt_size);
// 성공 시 할당된 메모리의 주소 값, 실패 시 NULL 반환
/*
malloc 함수와 달리 calloc 함수는 두 개의 숫자를 인자로 전달
첫 번째 전달인자로는 할당할 블록의 갯수 정보, 두 번째 전달인자로는 블록 하나당 바이트 크기의 정보 전달
"4 바이트 크기의 블록(elt_size) 30개를(elt_count) 힙 영역에 할당"
malloc 함수는 할당된 메모리 공간을 별도의 값으로 초기화 하지 않는다.
calloc 함수는 할당된 메모리 공간의 모든 비트를 0으로 초기화 시킨다.
해제는 마찬가지로 free 함수를 사용
*/
힙에 할당된 메모리 공간 확장 시 호출하는 realloc 함수
#include <stdlib.h>
void * realloc(void * ptr, size_t, size);
// 성공 시 새로 할당된 메모리의 주소 값, 실패 시 NULL 반환
/*
첫 번째 전달인자로 확장하고자 하는 힙 메모리의 시작 주소 값을 전달
두 번째 전달인자로 확장하고자 하는 메모리의 전체 크기를 전달
*/
int main(void)
{
int * arr = (int *)malloc(sizeof(int)*3); // 길이가 3인 int형 배열 할당
. . . .
arr = (int *)realloc(arr, sizeof(int)*5); // 길이가 5인 int형 배열로 확장
. . . .
}
/*
위 코드의 실행결과는 반환 값을 기준으로 다음과 같이 두 가지로 구분이 된다.
"malloc 함수가 반환한 주소 값과 realloc 함수가 반환한 주소 값이 같은 경우"
"malloc 함수가 반환한 주소 값과 realloc 함수가 반환한 주소 값이 같지 않은 경우"
전자는 기존에 할당된 메모리 공간의 뒤를 이어서, 확장할 영역이 넉넉한 경우
*/