프로그램을 구현하다보면 전역변수와 지역변수로는 해결되지 않는, 다른 유형의 변수를 필요로 하게되는 경우가 생긴다. 이러한 생성과 소멸의 시기가 지역변수나 전역변수와 다른 유형의 변수는 malloc함수와 free 함수를 통해 heap영역에 할당하거나 소멸할 수 있다.
이런 malloc 함수의 호출을 통한 메모리 공간의 할당을 가리켜, 메모리의 동적 할당(dynamic allocation)이라고 한다.
정적 메모리 할당과 동적 메모리 할당의 차이 -
- 동적 메모리 할당은 프로그램 실행시간(러닝타임) 동안 사용할 메모리 공간을 할당하고, 사용이 종료되면, 운영체제가 사용할 수 있도록 반납한다. 그리고 다음에 요구가 오면 재할당 할 수 있다.
반면, 정적 메모리 할당은 프로그램이 실행되는 순간 프로그램이 사용할 메모리 크기를 고려하여 메모리 할당이 이루어진다.
프로그래밍을 하다보면, 전역변수와 지역변수같은 정적변수로는 해결되지 않는 경우들이 있다. 다음 예시 코드들을 통해 알아보자.
1. #include <stdio.h>
2. #include <stdlib.h>
3.
4. char * ReadMemberName(void)
5. {
6. char name[30];
7. printf("이름을 입력하세요 : ");
8. gets(name);
9. return name;
10. }
11.
12. int main(void)
13. {
14. char * name1;
15. char * name2;
16.
17. name1 = ReadMemberName();
18. printf("name1 = %s\n", name1);
19. name2 = ReadMemberName();
20. printf("name2 = %s\n", name2);
21.
22. return 0;
13. }
다음 ReadMemberName 함수는 문자열(이름)을 입력받고, 입력받은 문자열의 주소값을 반환하는 함수이다.
-> 크기가 10인 char형 배열name에 gets함수를 통해 입력받은 문자열을 저장한 뒤, 배열의 이름'name'(배열의 첫 번째 인자의 주소값)을 반환.
배열 name은 ReadMemberName 함수 내에 지역적으로 선언되었기 때문에, 함수가 반환(종료)되면, 소멸된다.
그럼 17, 18번째 코드를 보자, 먼저 17번째 코드에서 char형 포인터변수 name1이 함수 ReadMemberName을 가리킨다(호출한다). 그럼, ReadMemberName함수는 입력받은 문자열의 주소값을 반환하고 종료된다. 이때 이 함수 내에 지역적으로 선언되었던 배열 name은 소멸된다. 입력받은 문자열(이름)을 저장하는 공간인 배열 name자체가 소멸된 것이기 때문에 당연히 해당 배열에 저장되어 있던 문자열 또한 같이 소멸된다.
하지만 18번째 코드를 보면, 이미 소멸된 값인 name1을 다시 호출하고 있다. 그러므로 17, 18번째 코드는 작동하지 않는 코드이다. (19, 20번째 코드 또한 같은 이유로 작동 X)
1. #include <stdio.h>
2. #include <stdlib.h>
3.
4. char name[30];
5. char * ReadMemberName(void)
6. {
7. printf("이름을 입력하세요 : ");
8. gets(name);
9. return name;
10. }
11.
12. int main(void)
13. {
14. char * name1;
15. char * name2;
16.
17. name1 = ReadMemberName();
18. printf("name1 = %s\n", name1);
19. name2 = ReadMemberName();
20. printf("name2 = %s\n", name2);
21.
22. printf("name1 = %s\n", name1);
23. printf("name2 = %s\n", name2);
24.
25. return 0;
26. }
이번엔 배열name을 전역적으로 선언해 보았다. 전역적으로 선언되었기 때문에 이제 배열 name은 프로그램의 시작과 함께 할당되고, 프로그램 종료시 소멸된다.
이제 문제의 17, 18, 19, 20번째 줄 코드들을 보자. 먼저, 17번째 줄을 통해 ReadMemberName함수를 호출하고, char형 포인터변수 name1은 함수를 통해 입력된 문자열의 주소값을 가리키게된다. (배열의 시작 주소값 = 배열의 이름 = 입력받은 문자열의 주소값)
아.. 설명충이여서 그냥 더 추가로 설명하려고 한다. C언어에는 '문자'를 저장하는 자료형 'Char'는 존재하지만, '문자열'을 저장하는 자료형은 따로 존재하지 않는다. 그렇기 때문에 문자열을 저장하기 위해서는 배열을 이용한다. (배열을 이용한 변수 형태의 문자열) 따라서, 배열에 문자열을 저장하면, 문자열의 주소값은 해당 배열의 시작 주소값과 같은 의미가 된다.
그럼 문자열의 끝은 어떻게 알 수 있나?
\0
이 붙게된다. 때문에 배열의 크기는 입력받을 문자열의 길이보다 크게 정해주어야 한다.h
e
l
l
0
\0
\0
\0
(문자열의 길이보다 배열의 크기가 더 크게 지정되었을 시, 문자열의 끝을 나타내는\0
포함 나머지 공간은 \0
으로 채워짐)결론-> 배열의 시작 주소값 = 배열의 이름 = 배열에 입력받은 문자열의 주소값
자, 이어서 설명하면 18번째 줄에서 반환된 문자열의 주소값을 가리키고 있는 포인터 변수 name1을 printf 함수 에서 호출한다. name배열이 전역으로 선언되었기 때문에 정상적으로 작동된다.
y
o
o
s
i
k
\0 ...
y
o
o
s
i
k
\0 ...
<= name 배열에 저장된 문자열 yoonsik, 이 문자열의 주소값(배열 name의 시작주소값)을 가리키고있는 포인터변수 name1.이 상태에서 ReadMemberName함수를 호출, name2에 반환된 문자열의 주소값 저장
name1, name2 -> [배열 name] C
h
o
y
o
o
s
i
k
\0 ...
<= 기존 name배열에 저장되어있던 문자열 yoonsik이 Choyoonsik으로 덮어써짐
최종 상태 -
name1, name2 -> [배열 name] C
h
o
y
o
o
s
i
k
\0 ...
때문에, 22, 23번째 줄에서 name1과 name2가 가리키고있는 문자열을 출력하면 같은이름 Choyoonsik이 출력된다. 이유는?
전역으로 선언된 배열 name에 yoonsik입력하고, 이를 name1에 저장, 그리고 배열을 새로 호출하고 Choyoonsik을 name2에 저장한 것이 아니라, name1이 가리키고 있는 전역적으로 선언된 기존 배열 name에 덮어썼기 때문 !