이 포스팅은 윤성우 님의 열혈 C를 기반으로 합니다.
C언어의 메모리
메모리는 OS에 의해 다음과 같이 4개의 영역으로 구분된다.
1. 코드 영역
2. 데이터 영역
3. 힙 영역
4. 스택 영역
1. 코드 영역 : 말 그대로 실행할 프로그램의 코드가 저장되는 공간이다.
2. 데이터 영역 : 전역 변수, static 변수가 할당되는 공간으로 프로그램의 시작 시 메모리 공간에 할당되어 프로그램 종료 시까지 남아있게 된다.
3. 힙 영역 : 프로그래머가 원하는 시점에 변수를 할당하고 또 소멸하도록 지원을 하는 변수들이 할당되는 영역
4. 스택 영역 : 지역변수와 매개변수가 할당되는 공간
그렇다면 이 3번 힙 영역에 대해서 자세히 알아보자.
메모리의 동적 할당
메모리 동적 할당을 배우기에 앞서서 다음의 코드를 확인해보자.
<ReadStringFault1.c>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
#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;
}
|
cs |
이 경우 ReadUserName 함수 내부에 반환되는 name은 지역변수로 스택 영역에 설정되어 있다. 따라서 함수를 빠져나가면 소멸되므로 실행이 정상적으로 되지 않는다. 그렇다면 이를 전역 변수로 바꾸면 어떻게 될까?
<ReadStringFault2.c>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
#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;
}
|
cs |
<실행결과>
전역변수로 설정하면 위의 실행결과와 마찬가지로 name1, name2가 덮어쓰기 때문에 이름 정보가 각각 유지되지 않는다. 그렇다면 이것을 해결하기 위해서 어떻게 해야 할까? 바로 동적 할당인 malloc, free를 사용하면 된다.
malloc 즉 memory allocation 메모리 할당이며
free 즉 malloc를 통해 할당된 메모리를 소멸시켜 주는 것 이다.
그런데 malloc의 반환형을 보면 void *이다. 앞서서 void형 포인터의 경우 단순하게 받기만 할 수 있는 즉 연산은 할 수 없는 형인데 왜 void 일까? 다음의 예시를 보면 알 수 있다.
길이가 7인 int형 배열의 공간을 할당하기 위해 void * ptr = malloc(sizeof(int) * 7); 이라고 하면 사용자 입장에서는 알 수 있지만 malloc에게 전달되는 것은 malloc(28) 일 뿐이다. 따라서 malloc는 인자로 전달된 숫자만큼의 메모리 공간을 할당만 해줄 수 있고 이를 사용하기 위해 형 변환을 해야 하는 것은 사용자에게 책임을 돌린다.
따라서 int * ptr = (int *)malloc(sizeof(int)*7); 와 같은 형태로 malloc를 사용해야 한다. 예시를 살펴보자.
<DynamicMemoryAllocation.c>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int* ptr = (int*)malloc(sizeof(int));
int* ptr1 = (int*)malloc(sizeof(int)*7);
*ptr = 20;
for (int i = 0; i < 7; i++) {
ptr1[i] = i + 1;
}
printf("%d \n", *ptr);
for (int i = 0; i < 7; i++) {
printf("%d ", ptr1[i]);
}
free(ptr);
free(ptr1);
return 0;
}
|
cs |
<실행결과>
free 호출은 할당한 메모리를 소멸해주므로 꼭 사용해야 한다.
이제 앞서 살펴봤던 문제들을 해결해보자.
<ReadStringRight.c>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
#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;
}
|
cs |
<실행결과>
정상적으로 작동하는 모습을 볼 수 있다.
부가적으로 realloc 함수가 있다. 이는 malloc로 할당된 영역을 재설정할 수 있게 하는 것이다.
'C' 카테고리의 다른 글
12. 구조체와 사용자 정의 자료형 2 (0) | 2021.06.27 |
---|---|
11. 구조체와 사용자 정의 자료형 (0) | 2021.06.27 |
10. 문자열 관련 함수 (0) | 2021.06.27 |
9. 함수 포인터 & void 포인터 (0) | 2021.06.26 |
8. 2차원 배열과 포인터 (0) | 2021.06.26 |