어떤 언어를 이용해서 프로그래밍을 하든지 프로그램이 하는 주요한
임무는 결국 데이타를 주고/받고 이를 가공하는 작업이다.
이는 사람이 사회에서 살아가기 위한 가장 주요한 일이 서로간의
대화인것과 마찬가지이다.
사람은 서로 대화를 하기 위해서 대화할 내용을 미리 어딘가에 저장하고
있어야 한다. 사교를 위한 대화를 위해서라면 머리에 저장되어 있는
(저장이라고 말하니 좀 이상하긴하다 --;) 데이타(경험)를 활용해서
대화를 하면 될것이고, 업무상 중요한 대화라면, 미리 노트를 하든지
해서 실제 대화때 중요한 내용을 빠트리지 않도록 준비를 해야 할것이다.
프로그램도 마찬가지로 어떤 수행을 위해서는 데이타를 어딘가에 저장해
두고 있어야 할것이다. 이러한 데이타 의 저장은 메모리공간
혹은 디스크 공간을 이용한다.
이문서는 데이타저장을 위해서 어떻게 메모리 공간을
이용해야 하는지에 대한 내용을 다루고 있다.
정적으로 메모리를 할당할경우 약간의 메모리 낭비가 있을수 있다.
보통 할당할 메모리를 결정할때, 최대 사용가능하다고 생각되는
메모리량보다 약간더 크게 잡는게 보통이기 때문이다.
그러나 크기의 한계를 명확히 알수 있을경우, 동적 메모리 할당보다
사용하기 쉽고, 버그가 발생할 확률도 적다는 장점을 가지고 있다.
보통 프로젝트를 진행하게 될경우 약간의 메모리 낭비보다는
버그의 발생을 더큰 프로그램 위험요소로 생각하기 때문에,
가능한한 정적 메모리 할당을 사용한다.
동적 메모리 할당을 사용할경우 메모리 누수, 혼동되는 포인터의
사용에 의한 잘못된 메모리 참조등 여러가지
문제를 발생시킬수 있기 때문이다. 포인터 잘못사용해서 발생하는
문제가 얼마나 프로그래머를 괴롭히는지는 말하지 않아도
잘 알고 있으리라 생각된다.
Dynamic Memory Allocation 이라고 불리우며,
말그대로 프로그램 실행중에 동적으로 메모리의 크기를 할당시켜줘야
할 필요가 있을경우 사용한다.
예를 들어서 간단한 에디터 프로그램을 만든다고 했을때,
보통 파일의 내용을 메모리 상에 읽어 들이게 될것이다.
그런데 파일의 크기가 얼마가 될지는 아무도 알수 없다.
파일이 작을경우 그 크기가 0이 될수도 있겠지만 파일이 클경우
수십 메가 바이트 혹은 그 이상이 될수도 있을것이기 때문이다.
이경우에는 정적 메모리 할당을 사용할수 없으며,
어쩔수 없이 동적 메모리 할당을 사용해야 할것이다.
"어쩔수 없이" 란 말을 붙인 이유는 되도록이면 동적 메모리 할당을
사용하는것 보다는 정적메모리 할당을 사용하는게 여러모로 이익이
많기 때문이다.
동적 메모리 할당을 위해서 C 는 주요한 몇가지 시스템 함수와
키워드를 제공한다. 이중 malloc 는 메모리 상에서 연속된
일정 크기의 공간을 할당받기 위해서 사용하는 가장
일반적인 함수이다.
void *malloc(size_t number_of bytes);
malloc() 함수는 인자로 할당받고자 하는 메모리의 크기를
byte 단위로 명시한다. 만약 메모리 할당이 성공했다면 malloc
함수는 할당된 메모리의 시작 위치를 가리키는 포인터를
반환한다. (포인터에 대한 내용은 데이타와 포인터)
void * 를 받는 이유는 void * 를 이용할경우
어떤 타입으로라도
형변환이 가능하기 때문이다.
만약 100 byte 의 문자를 저장하기 위한 공간을
할당받기 원한다면
다음과 같이 malloc() 함수를 호출하면 될것이다.
malloc() 을 통해서 메모리 할당요청을 받은 운영체제(커널)은
100 byte 크기만큼의 연속된 메모리를 할당하고,
할당된 메모리의
첫번째를 가리키는 주소값을 넘겨준다.
우리가 실제 *cp 를가지고
하는 여러가지 데이타 관련된 조작은 할당된 메모리의 주소값을
이용해서 이루어지게 된다.
1 번 결과는 포인터 *ch 가 할당된 곳의 주소값이다.
2 번 결과는 *ch 가 포인터(가리키는)하는 곳의 첫번째 메모리
의 주소값이다. *ch 가 가리키는 곳은 buf 가 저장된 메모리의
첫번째 주소 값이므로 bffff7f0 이 세팅되었다. 3번, 4번 결과
는 *ch 가 실제 buf의 주소를 가리키고 있음을 확인하기
위해 출력한 값이다. 이들 결과를 보면 실제로 포인터가
어떻게 값을 가리키고 있는지 확인할수 있을것이다.
5 번부터 8 번까지는 malloc()을 호출함으로써 메모리
구성이 실제로 어떻게 되는지를 보여준다.
5 번은 cp 가 위치한 곳의 주소이니까 별로 신경쓸필요는 없다.
6 번은 malloc() 하기전에 *cp 가 가리키고 있는 곳의
주소 값을 보여주는데 메모리 할당되어 있지않은경우이다.
위의 경우 NULL 을 가리키고 있는데,
이것은 상황에 따라 변한다.
다시 말해서 메모리 할당하지 않았을경우
임의의 영역을 가리킨다고
보면 무난하다.
7 번이 malloc()을 이용해서 메모리
할당한후 가리키는 곳의 주소인데, malloc() 하기전과 비교해
보면 가리키고 있는 곳의 주소가
명확하게 정해져 있음을 확인할수 있다.
8,9 번은 각각 buf 와 buf2 를 대입했다.
당연한 얘기이지만 메모리를 사용하기 위해서는 적당한 공간을
할당해주어야 한다. 바로 위의 그림에서 malloc 하기전에
*cp 가 가리키는 주소를 보면 상황에 따라 달라진다고 했다.
그럼으로 할당되지 않은 포인터에 데이타를 입력하면
임의의 주소에 어떤 값을 입력하는 꼴이 된다.
운이 좋으면 임의의 주소 영역에 데이타를 충분히 저장할 공간이
확보되어 있어서 에러없이 프로그램이 실행될수도 있지만,
다른 프로세스가 차지하고 있는 메모리 영역을 침범할수도 있다.
이럴경우 메모리 영역에 대한
우선권은 먼저 획득한 프로세스에게 있음으로, 커널은
이 메모리영역에 데이타를 쓰려고 하는 프로세스를
강제 종료시킬것이다(세그먼트 폴트 에러)
위에서 메모리 할당을 하지 않았음에도 불구하고
프로그램이 제대로 실행되면 운이 좋은경우라고 했는데,
사실 이경우는 운이 좋은경우가 아니고
운이 나쁜경우가 된다. 언뜻 보기에 정상적으로
실행되는것 처럼 보일수 있기 때문에 디버깅 작업을
어렵게 만들수 있기 때문이다(다른 프로세스의 메모리 영역을
침범할 가능성을 가지고 있는 불완전한 코드이다).
잘 돌다가 어느날 아침에 확인해보니까 프로그램이
죽게 될 확률이 높다. 사용하는 메모리의
공간이 작을수록(크면은 다른 영역을 침범할 가능성이
크다) 운 좋게(나쁘게) 제대로 작동될 확률이 크다.
그러므로 아래와 같은 코드는 심각하게 잘못된
코드이다.(아마 어떤경우에는 제대로 실행되고,
어떤 경우에는 세그먼트 폴트가 떨어질것이다)
메모리의 크기를 조정하고자 할때 사용한다.
에디터 프로그램을 예로 들어보자면, 2가지 메모리 할당을
할수 있을건데, 파일의 크기를 읽은다음 파일의 크기만큼
한번에 메모리 할당을 해버리는 방법과 1024 바이트 정도로
할당하고, 파일을 읽어들이다가 1024 를 초과하게 되면,
realloc 를 이용해서 1024를 더 할당해주는 방법이 있다.
어느걸 사용하든지 관계는 없지만, realloc 은 기본행동이
만약 연속된 메모리 공간이 충분하지 않을경우
연속된 메모리 공간을 할당할수 있도록 새로 공간을 잡게
되며, 이와중에 기존의 데이타가 복사되므로 상당히
많은 비용이 소모될수 있다.(실제 테스트 해보면 알겠지만
malloc 에 비해서 눈에 띄게 많은 시간이 소모된다)
[root@localhost doc]# ./realloc
100 : 8049770
10000 : 8049770
1000000 : 4015e008
Value is (111)
위의 값은 상황에 따라 변할수 있다. 100, 10000 은
같은 메모리 주소를 사용하고 있지만, 1000000 이 되면서
메모리의 위치가 변경되었음을 알수 있다. 아무래도 100 과
10000 의 경우 그리 큰차이가 나지 않기 때문에 현재
위치에서 연속된 메모리 공간을 확보하기가 수월하지만
값이 커질수록 연속된 메모리 공간을 확보가 어려워지기
때문에, 어쩔수 없이 메모리 이동이 일어나게 된다.
메모리 위치 이동이 일어난다 하더라도 값은 그대로 복사되고
있음을 알수 있다.
sizeof 는 C 에서 제공하는 키워드로 해당 자료형의 크기를
돌려준다. sizeof 는 메모리 할당에 있어서 꽤 중요한 역할을
가진다. 이유는 각 자료형마다 차지하는 byte 크기가 틀리고,
같은 자료형이라 할지라도 운영체제에 따라 그 크기가
달라질수 있기 때문이다. 예를들어 int 형 자료 4개를
저장하기 위해서 다음과 같이
메모리 크기를 할당했다고 하자.
int *ip;
ip = (int *)malloc(16);
int 형은 보통의 경우 4byte 이니까
4개의 자료를 저장하기 위해서
는 16 만큼의 크기가 필요한건 확실하다.
그러나 int 형의 크기가
4byte 인것은 보통의 경우이고 2byte 혹은 8byte 인 경우가
있을수도 있다. 그럴경우 위의 프로그램은 현재 운영체제에서는
문제 없겠지만, 다른 운영체제로 포팅하고자
할때 문제가 될수도 있다.
이런 문제를 없애기 위해서 sizeof 키워드를 제공받아서
사용한다. 이 키워드를 사용하면 운영체제에서
사용하는 자료형의 크기를 돌려주게
됨으로 위에서와 같은 문제점이
발생하지 않는다.
int *ip;
ip = (int *)malloc(sizeof(int)*4);
printf("int size is %d\n", sizeof(int));
int 자료형을 위해서 4byte 공간을 필요로 하는 운영체제라면
16, 8byte 만큼을 필요로 하는 운영체제라면 32byte 만큼을
할당받을수 있게 될것이다.
실제 warn_mem 의 실행결과 메모리 누수가 생기는지
확인을 위해서 간단한 스크립트를 만들어서 테스트를 해보도록
하자.
[root@coco /root]# while [ 1 ]
> do
> ps -aux | grep warn_mem | grep -v vi | grep -v grep
> sleep 1
> done
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 11719 0.0 0.3 1436 400 ttypc S 11:24 0:00 ./warn_mem
root 11719 0.0 0.3 1636 408 ttypc S 11:24 0:00 ./warn_mem
root 11719 0.0 0.3 1736 412 ttypc S 11:24 0:00 ./warn_mem
root 11719 0.0 0.3 1836 416 ttypc S 11:24 0:00 ./warn_mem
root 11719 0.0 0.3 1936 420 ttypc S 11:24 0:00 ./warn_mem
root 11719 0.0 0.3 2036 424 ttypc S 11:24 0:00 ./warn_mem
root 11719 0.0 0.3 2236 432 ttypc S 11:24 0:00 ./warn_mem
root 11719 0.0 0.3 2336 436 ttypc S 11:24 0:00 ./warn_mem
ps 의 헤더 부분은 원래 스크립트의 실행결과에는 표시되지
않지만 ps 결과의 필드 구분을 쉽게 하기 위해서 추가 시켰다.
위의 결과를 보면 warn_mem 프로세스에서 점유하는 메모리의
크기가 지속적으로 증가되고 있음을 볼수 있다. 이러한 메모리
누수는 프로그램과 시스템에 매우 치명적일수 있다.
특히 이러한 종류의
문제는 컴파일러에서 처리를 해주지 않기 때문에 나중에
문제점을 찾기가 매우 곤란해진다. 흔히 말하는 몇일 잘돌다가
죽는 프로그램이 될 가능성이 크다.
그러므로 사용하지 않는 메모리공간은 반드시 운영체제에게
되돌려 주어야 한다. C 는 동적으로 할당된 메모리의 해제를
위해서 free() 를 제공한다.
다음은 warn_mem.c 의 메모리 누수 문제를 free() 를 통해서
해결한 코드이다.
링크드 리스트는 그리 간단히 다룰수 있는 주제가 아니다.
링크드 리스트에 대한 자세한 설명은 자료구조를 다룰 기회가 있으면
그때 하기로 하고, 여기에서는 개념정도만 설명하도록 하겠다.
링크드 리스트는 말그대로 리스트를 만들기 위해서 사용되는
자료구조이다. 리스트를 만들기 위해서는 자료의 연속된 순서를
만들어 줘야 한다. 그럴려면 다음자료의 정보가 무엇인지를
알아야 하는데, 링크드 리스트란 다음자료의 정보가 무엇인지를
알고 있는 구조를 말한다. 여기서 다음자료의 정보란 다음 자료가
위치하고 있는 주소의 값이 될것이다.
P: Pointer
+--------++-+ +--------++-+ +--------++-+
| Data 1 ||P| | Data 2 ||P| | Data 3 ||P|
+--------++-+ +--------++-+ +--------++-+
| | | | |
+-----+ +-----+ +--- NULL
대충 위와 같은 방식으로 연결된다. 리스트를 이루고 있는
리스트 멤버들은 실제 Data 와 더불어 다음 데이타의 주소정보를
가지고 있는 Pointer 을 가지고 있으며, 프로그래머는 이
Pointer 정보를 이용해서, 데이타에 접근할수 있게 된다.
위의 그림을 보면 알겠지만 Data 3 에 접근하기 위해서는
Data 1 번부터 순차적으로 접근해야 한다는 것을 알수 있다.
위에서 말했지만 자료구조 자체의 설명을 목적이 아닌 관계로
가장 단순한 형태의 링크드 리스트를 구현하도록 하겠다.
이 링크드 리스트는 다음과 같은 기능을 가진다.
데이타 삽입
멤버 데이타를 삽입한다. 최초 데이타가 삽입될때는
다음 데이타가 없음으로 다음데이타를 가리키는
Pointer 은 NULL 이 될것이다. 만약 두번째 데이타가
들어온다면 첫번째 데이타의 Pointer 은 두번째
데이타의 위치를 가리키게 될것이다. 두번째 데이타는 다시
NULL 을 가리키게 될것이다.
데이타 삭제
데이타 삭제를 제대로 구현하고자 한다면, 찾기후
삭제를 구현해야 하겠으나 여기에서는 POP 스타일의
삭제를 구현하도록 한다.(가장 먼저 들어온 데이타가
가장 먼저 삭제되는 방식)
데이타 출력
역시 간단하게 처음의 리스트 멤버부터 순차적으로
검색해가면서 데이타를 출력하는 방식으로 구현할
것이다.
위의 3가지 구현은 링크드 리스트 뿐만 아니라 다른 자료구조에서도
가장 기본이 되는것들이며 위의 구현방식의 약간 다른 응용으로
만들어진다. 좀더 난이도 있는 구현은 각자 공부삼아서 해보기
바란다.
실제적으로 free() 함수로 동적메모리 할당을 해제했다고 하더라도 어플리케이션 단위에서는 해당 메모리를 그대로 가지고 있는것으로 나타났다.
같은 코드를 가지고 RedHat linux 배포판에서 test 를 해보았다. OS 환경은 RH 8.0, gcc 버전은 3.2(20020903 release) 버전이다.
indra@ ~test> ./free &
[1] 25255
indra@ ~test> while :; do ps -aux | grep "./free"; sleep 1 ; done
indra 25255 0.0 0.0 1308 216 pts/5 S 13:07 0:00 ./free
indra 25255 0.0 0.0 1308 216 pts/5 S 13:07 0:00 ./free
indra 25255 0.0 0.0 1308 216 pts/5 S 13:07 0:00 ./free
Executed malloc function. (in main function. 15 line)
indra 25255 0.0 0.1 1049892 312 pts/5 S 13:07 0:00 ./free
indra 25255 0.0 0.1 1049892 312 pts/5 S 13:07 0:00 ./free
indra 25255 0.0 0.1 1049892 312 pts/5 S 13:07 0:00 ./free
indra 25255 0.0 0.1 1049892 312 pts/5 S 13:07 0:00 ./free
Executed free function. (in main function. 18 line)
indra 25255 0.0 0.1 1312 308 pts/5 S 13:07 0:00 ./free
indra 25255 0.0 0.1 1312 308 pts/5 S 13:07 0:00 ./free
indra 25255 0.0 0.1 1312 308 pts/5 S 13:07 0:00 ./free
indra 25255 0.0 0.1 1312 308 pts/5 S 13:07 0:00 ./free
indra 25255 0.0 0.1 1312 308 pts/5 S 13:07 0:00 ./free
Executed exit function. (in main function. 21 line)
[1]+ Done ./free
^C
indra@ ~test>
SunOS 에서의 메모리 관리가 user 의 눈으로 보이는것만 다른것인지 아니면 실제 free() 후 메모리 관리가 효율성을 위한 측면으로 다른 OS 와 관리체계가 다른지는 아직까지 불 분명 하다.
이에 대한 yundream 님의 답변:
http://www.joinc.co.kr/modules.php?op=modload&name=Forum&file=viewtopic&topic=28365&forum=1&2
솔라리스 운영체제에서 테스트 한거 아닙니까 ?
리눅스상에서는 free 했을경우 제대로 해제가 되는데,
솔라리스에서는 해제가 안되더군요.
정확히 말하면 안되는것처럼 보인다고 해야 정확한 표현일듯 싶은데,
저도 예전에 솔라에서 작업하다가,
malloc 후 free 가 안되는 문제로 꽤 고민을 한적이 있었습니다.
몇몇 문서를 찾아봤더니, 메모리관련작업 안정성을 확보하기 위해서라고 되어 있는것 같기는 하던데
확실히는 잘 모르겠습니다.
어쨋든 저것때문에 메모리 누수와 같은 문제가
발생하지 않습니다.
솔라에서 메모리관리를 어떻게 하는지좀 알아봐야 겠네요.
저문제로 가끔 짜증날때가 있는데,
어떤 이유로 일시적으로 한 100메가 이상 메모리를 잡아서 쓰게되었다면 free를 해도 ps 상에서는
100메가를 그대로 잡고 있는걸로 보이기 때문에,
프로그래머 입장에서는 문제가 되지 않겠지만,
가끔 고객에게 프로그램을 제공할때 문제가 될수도 있죠.
"왜 이렇게 메모리를 많이 잡아먹어요? 문제 있는거 아닌가요?"
이런 문제가 발생할수 있습니다.
설명을 해도 쉽게 납득을 하지 못하죠.
마지막으로 realloc() 을 통한 메모리 재 할당을 test 하여 보았다.
linux 에서는 realloc() 을 사용시, 메모리 사이즈가 재 설정되는 양상을
보였지만 SunOS 에서는 역시 처음 malloc() 으로 할당된 메모리 사이즈를
유지하고 있었다.
Contents
동적 메모리할당
윤 상배
dreamyun@yahoo.co.kr
1절. 소개
어떤 언어를 이용해서 프로그래밍을 하든지 프로그램이 하는 주요한 임무는 결국 데이타를 주고/받고 이를 가공하는 작업이다. 이는 사람이 사회에서 살아가기 위한 가장 주요한 일이 서로간의 대화인것과 마찬가지이다.
사람은 서로 대화를 하기 위해서 대화할 내용을 미리 어딘가에 저장하고 있어야 한다. 사교를 위한 대화를 위해서라면 머리에 저장되어 있는 (저장이라고 말하니 좀 이상하긴하다 --;) 데이타(경험)를 활용해서 대화를 하면 될것이고, 업무상 중요한 대화라면, 미리 노트를 하든지 해서 실제 대화때 중요한 내용을 빠트리지 않도록 준비를 해야 할것이다.
프로그램도 마찬가지로 어떤 수행을 위해서는 데이타를 어딘가에 저장해 두고 있어야 할것이다. 이러한 데이타 의 저장은 메모리공간 혹은 디스크 공간을 이용한다.
이문서는 데이타저장을 위해서 어떻게 메모리 공간을 이용해야 하는지에 대한 내용을 다루고 있다.
2절. 메모리 할당
메모리 할당을 위한 방법은 크게 2가지가 있다. 정적메모리 할당과 동적 메모리 할당이 그것인데, 동적 메모리 할당을 설명하기 전에 정적 메모리 할당에 대해 간단히 알아보도록 하겠다.
2.1절. 정적 메모리 할당에 대해서
Static Memory Allocation 이라고 불리우며, 메모리의 크기가 미리 고정시켜서 할당하는 것을 말한다. 일반적으로 메모리크기를 할당하는 쉬운 방법으로, 할당시켜줘야할 메모리의 한계 크기를 명확히 알고 있을경우 사용한다.
예를 들어 주소를 저장하기 위한 메모리 공간이 필요하다고 할때, 우리는 주소를 저장하기 위해서 어느정도의 메모리 공간이 필요한지를 대충 계산할수 있다. 주소길이가 아무리 길어봐야 256 자를 넘지 않을것이기 때문이다.
이러한 정적 메모리 할당은 프로그램 시작시에 미리 고정시켜서 할당시켜 버린다.(그런 이유로 Static 이란 단어가 붙는다.)
그러나 크기의 한계를 명확히 알수 있을경우, 동적 메모리 할당보다 사용하기 쉽고, 버그가 발생할 확률도 적다는 장점을 가지고 있다. 보통 프로젝트를 진행하게 될경우 약간의 메모리 낭비보다는 버그의 발생을 더큰 프로그램 위험요소로 생각하기 때문에, 가능한한 정적 메모리 할당을 사용한다. 동적 메모리 할당을 사용할경우 메모리 누수, 혼동되는 포인터의 사용에 의한 잘못된 메모리 참조등 여러가지 문제를 발생시킬수 있기 때문이다. 포인터 잘못사용해서 발생하는 문제가 얼마나 프로그래머를 괴롭히는지는 말하지 않아도 잘 알고 있으리라 생각된다.
2.2절. 동적 메모리 할당에 대해서
Dynamic Memory Allocation 이라고 불리우며, 말그대로 프로그램 실행중에 동적으로 메모리의 크기를 할당시켜줘야 할 필요가 있을경우 사용한다.
예를 들어서 간단한 에디터 프로그램을 만든다고 했을때, 보통 파일의 내용을 메모리 상에 읽어 들이게 될것이다. 그런데 파일의 크기가 얼마가 될지는 아무도 알수 없다. 파일이 작을경우 그 크기가 0이 될수도 있겠지만 파일이 클경우 수십 메가 바이트 혹은 그 이상이 될수도 있을것이기 때문이다. 이경우에는 정적 메모리 할당을 사용할수 없으며, 어쩔수 없이 동적 메모리 할당을 사용해야 할것이다.
"어쩔수 없이" 란 말을 붙인 이유는 되도록이면 동적 메모리 할당을 사용하는것 보다는 정적메모리 할당을 사용하는게 여러모로 이익이 많기 때문이다.
2.2.1절. malloc, realloc, sizeof, memset, free
2.2.1.1절. malloc
동적 메모리 할당을 위해서 C 는 주요한 몇가지 시스템 함수와 키워드를 제공한다. 이중 malloc 는 메모리 상에서 연속된 일정 크기의 공간을 할당받기 위해서 사용하는 가장 일반적인 함수이다.
만약 100 byte 의 문자를 저장하기 위한 공간을 할당받기 원한다면 다음과 같이 malloc() 함수를 호출하면 될것이다.
아래의 예제를 실행시켜보면 좀더 쉽게 이해가 가능할것이다.
예제 : malloc_1.c
5 번부터 8 번까지는 malloc()을 호출함으로써 메모리 구성이 실제로 어떻게 되는지를 보여준다. 5 번은 cp 가 위치한 곳의 주소이니까 별로 신경쓸필요는 없다. 6 번은 malloc() 하기전에 *cp 가 가리키고 있는 곳의 주소 값을 보여주는데 메모리 할당되어 있지않은경우이다. 위의 경우 NULL 을 가리키고 있는데, 이것은 상황에 따라 변한다. 다시 말해서 메모리 할당하지 않았을경우 임의의 영역을 가리킨다고 보면 무난하다. 7 번이 malloc()을 이용해서 메모리 할당한후 가리키는 곳의 주소인데, malloc() 하기전과 비교해 보면 가리키고 있는 곳의 주소가 명확하게 정해져 있음을 확인할수 있다. 8,9 번은 각각 buf 와 buf2 를 대입했다.
2.2.1.1.1절. 메모리를 사용하기 위해서는 반드시 할당해야 한다.
당연한 얘기이지만 메모리를 사용하기 위해서는 적당한 공간을 할당해주어야 한다. 바로 위의 그림에서 malloc 하기전에 *cp 가 가리키는 주소를 보면 상황에 따라 달라진다고 했다. 그럼으로 할당되지 않은 포인터에 데이타를 입력하면 임의의 주소에 어떤 값을 입력하는 꼴이 된다. 운이 좋으면 임의의 주소 영역에 데이타를 충분히 저장할 공간이 확보되어 있어서 에러없이 프로그램이 실행될수도 있지만, 다른 프로세스가 차지하고 있는 메모리 영역을 침범할수도 있다. 이럴경우 메모리 영역에 대한 우선권은 먼저 획득한 프로세스에게 있음으로, 커널은 이 메모리영역에 데이타를 쓰려고 하는 프로세스를 강제 종료시킬것이다(세그먼트 폴트 에러)
위에서 메모리 할당을 하지 않았음에도 불구하고 프로그램이 제대로 실행되면 운이 좋은경우라고 했는데, 사실 이경우는 운이 좋은경우가 아니고 운이 나쁜경우가 된다. 언뜻 보기에 정상적으로 실행되는것 처럼 보일수 있기 때문에 디버깅 작업을 어렵게 만들수 있기 때문이다(다른 프로세스의 메모리 영역을 침범할 가능성을 가지고 있는 불완전한 코드이다). 잘 돌다가 어느날 아침에 확인해보니까 프로그램이 죽게 될 확률이 높다. 사용하는 메모리의 공간이 작을수록(크면은 다른 영역을 침범할 가능성이 크다) 운 좋게(나쁘게) 제대로 작동될 확률이 크다.
그러므로 아래와 같은 코드는 심각하게 잘못된 코드이다.(아마 어떤경우에는 제대로 실행되고, 어떤 경우에는 세그먼트 폴트가 떨어질것이다)
2.2.1.2절. realloc
메모리의 크기를 조정하고자 할때 사용한다. 에디터 프로그램을 예로 들어보자면, 2가지 메모리 할당을 할수 있을건데, 파일의 크기를 읽은다음 파일의 크기만큼 한번에 메모리 할당을 해버리는 방법과 1024 바이트 정도로 할당하고, 파일을 읽어들이다가 1024 를 초과하게 되면, realloc 를 이용해서 1024를 더 할당해주는 방법이 있다.
어느걸 사용하든지 관계는 없지만, realloc 은 기본행동이 만약 연속된 메모리 공간이 충분하지 않을경우 연속된 메모리 공간을 할당할수 있도록 새로 공간을 잡게 되며, 이와중에 기존의 데이타가 복사되므로 상당히 많은 비용이 소모될수 있다.(실제 테스트 해보면 알겠지만 malloc 에 비해서 눈에 띄게 많은 시간이 소모된다)
예제 : realloc.c
2.2.1.3절. sizeof
sizeof 는 C 에서 제공하는 키워드로 해당 자료형의 크기를 돌려준다. sizeof 는 메모리 할당에 있어서 꽤 중요한 역할을 가진다. 이유는 각 자료형마다 차지하는 byte 크기가 틀리고, 같은 자료형이라 할지라도 운영체제에 따라 그 크기가 달라질수 있기 때문이다. 예를들어 int 형 자료 4개를 저장하기 위해서 다음과 같이 메모리 크기를 할당했다고 하자.
이런 문제를 없애기 위해서 sizeof 키워드를 제공받아서 사용한다. 이 키워드를 사용하면 운영체제에서 사용하는 자료형의 크기를 돌려주게 됨으로 위에서와 같은 문제점이 발생하지 않는다.
2.2.1.4절. free
malloc() 은 메모리 할당을 커널에 요청하는 시스템 함수이다. 그러므로 일단 malloc()에 의해서 할당받은 메모리는 프로세스가 종료될때까지 커널에 의해서 보호받게 된다.
이말은 malloc() 를 잘못 사용할경우 쓸데없는 메모리 공간의 낭비 를 가져 올수 있으며, 심각할경우 메모리 누수를 가져올수 있다는 뜻이된다. 다음의 예를 보자
warn_mem.c
그러므로 사용하지 않는 메모리공간은 반드시 운영체제에게 되돌려 주어야 한다. C 는 동적으로 할당된 메모리의 해제를 위해서 free() 를 제공한다. 다음은 warn_mem.c 의 메모리 누수 문제를 free() 를 통해서 해결한 코드이다.
free_mem.c
3절. 동적 메모리 할당의 응용
이번장에서는 동적메모리 할당의 응용 예제를 만들어 볼것이다. 만들 응용 예제는 linked list 이다.
3.1절. 링크드 리스트 에 대해서
링크드 리스트는 그리 간단히 다룰수 있는 주제가 아니다. 링크드 리스트에 대한 자세한 설명은 자료구조를 다룰 기회가 있으면 그때 하기로 하고, 여기에서는 개념정도만 설명하도록 하겠다.
링크드 리스트는 말그대로 리스트를 만들기 위해서 사용되는 자료구조이다. 리스트를 만들기 위해서는 자료의 연속된 순서를 만들어 줘야 한다. 그럴려면 다음자료의 정보가 무엇인지를 알아야 하는데, 링크드 리스트란 다음자료의 정보가 무엇인지를 알고 있는 구조를 말한다. 여기서 다음자료의 정보란 다음 자료가 위치하고 있는 주소의 값이 될것이다.
3.2절. 동적 메모리 할당을 통한 링크드 리스트의 구현
위에서 말했지만 자료구조 자체의 설명을 목적이 아닌 관계로 가장 단순한 형태의 링크드 리스트를 구현하도록 하겠다. 이 링크드 리스트는 다음과 같은 기능을 가진다.
멤버 데이타를 삽입한다. 최초 데이타가 삽입될때는 다음 데이타가 없음으로 다음데이타를 가리키는 Pointer 은 NULL 이 될것이다. 만약 두번째 데이타가 들어온다면 첫번째 데이타의 Pointer 은 두번째 데이타의 위치를 가리키게 될것이다. 두번째 데이타는 다시 NULL 을 가리키게 될것이다.
데이타 삭제를 제대로 구현하고자 한다면, 찾기후 삭제를 구현해야 하겠으나 여기에서는 POP 스타일의 삭제를 구현하도록 한다.(가장 먼저 들어온 데이타가 가장 먼저 삭제되는 방식)
역시 간단하게 처음의 리스트 멤버부터 순차적으로 검색해가면서 데이타를 출력하는 방식으로 구현할 것이다.
3.3절. 예제
linked_list.c
다음은 필자의 컴퓨터에서 실행시킨 결과다. 번호는 설명을 위해서 붙인것이다.
솔라리스에서의 메모리 할당
'''linked list 예제소스에 warning 이 있어 수정하여 보았습니당'''
Recent Posts
Archive Posts
Tags