이 포스팅은 윤성우 님의 열혈 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

+ Recent posts