C/C++는 Java, C#과 같은 고수준 프로그래밍 언어와 달리 개발자가 직접 메모리 영역 할당 및 해제를 수행해야 한다. 이러한 할당과 해제 작업은 스택(stack), 힙(heap) 세그먼트에서 발생한다.
운영체제는 프로그램을 메모리에 적재하여 프로세스를 생성하고, 프로세스 식별자(PID: Process IDentifier)를 부여한다. 프로세스는 정상 종료 또는 SIGTERM, SIGINT, SIGKILL 신호(signal)가 발생할 때까지 실행된다. 운영체제는 프로세스 생성 시 프로세스를 위한 메모리를 할당한 후 미리 정의된 메모리 레이아웃을 적용한다.
일반적인 프로세스의 메모리 레이아웃은 여러 개의 세그먼트들로 분할된다.
실행 가능한 목적 파일이 메인 메모리에 적재되어 프로세스가 실행될 때, 목적 파일로부터 구성되는 영역을 정적 메모리 레이아웃, 실행 중 동적으로 생성되는 영역을 동적 메모리 레이아웃이라고 한다. 두 레이아웃 모두 사전 정의된 세그먼트의 집합이다. 정적 메모리 레이아웃은 컴파일러에 의해 목적 파일에 사전 작성되고, 동적 메모리 레이아웃은 프로그램 명령어에 따라 실행 중 작성된다.
코드 박스 4-1 [예제 4-1] 간단한 C 프로그램
int main(int argc, char** argv) {
return 0;
}
셀 박스 4-1 리눅스에서 gcc를 사용해 [예제 4-1] 컴파일하기
$ gcc 4_1.c -o 4_1-linux.out
셀 박스 4-2 ex4_1-linux.out의 정적 세그먼트를 살펴보는 사이즈 명령어의 사용
$ size 4_1-linux.out
text data bss dec hex filename
1235 544 8 1787 6fb 4_1-linux.out
4_1-linux.out 파일에는 리눅스 운영체제 전용으로 사전 정의된 정적 메모리 레이아웃이 포함된다. size 명령어를 사용하면 정적 메모리 레이아웃을 출력할 수 있다. 저수준 메모리에 대한 세부 사항은 플랫폼마다 상이하다.
BSS(Block Started by Symbol, 심볼로 시작되는 블록)는 역사적으로 초기화되지 않은 워드를 위해 예약된 영역을 나타내고자 사용되었다. 기본적으로 초기화되지 않은 전역 변수, 0으로 초기화된 전역 변수를 위해 사용된다.
코드 박스 4-2 [예제 4-2] 초기화되지 않았거나 0으로 설정된 전역 변수 몇 개를 갖는 간단한 C 프로그램
int global_var1;
int global_var2;
int global_var3 = 0;
int main(int argc, char** argv) {
return 0;
}
셀 박스 4-5 size 명령어로 4_2-linux.out의 정적 세그먼트를 확인하기
$ gcc 4_2.c -o 4_2-linux.out
$ size 4_2-linux.out
text data bss dec hex filename
1235 544 16 1795 703 4_2-linux.out
BSS 세그먼트의 크기가 늘어났음을 확인할 수 있다.
데이터 세그먼트에는 0이 아닌 값으로 초기화된 전역 변수가 저장된다.
코드 박스 4-3 [예제 4-3] 초기화된 것과 초기화되지 않은 전역 변수를 갖는 간단한 C 프로그램
int global_var1;
int global_var2;
int global_var3 = 0;
double global_var4 = 4.5;
char global_var5 = 'A';
int main(int argc, char** argv) {
return 0;
}
셀 박스 4-7 size 명령어로 4_3-linux.out의 정적 세그먼트 보기
$ gcc 4_3.c -o 4_3-linux.out
$ size 4_3-linux.out
text data bss dec hex filename
1235 553 20 1808 710 4_3-linux.out
데이터 세그먼트의 크기가 9바이트 증가했음을 확인할 수 있다.
코드 박스 4-4 초기화된 것과 초기화되지 않은 정적 변수 2개의 선언
void func() {
static int i;
static int j = 1;
...
}
위 예제에서 런타임에 func 함수는 데이터 세그먼트 또는 BSS 세그먼트에 위치한 두 정적 변수(static variable)에 접근한다.
코드 박스 4-5 [예제 4-4] 데이터 세그먼트에 작성된 초기화된 전역 변수
int x = 33;
int y = 0x12153467;
char z[6] = "ABCDE";
int main(int argc, char** argv) {
return 0;
}
셀 박스 4-9 objdump로 데이터 세그먼트의 내용 확인하기
$ gcc 4_4.c -o 4_4.out
$ objdump -s -j .data 4_4.out
4_4.out: file format elf64-x86-64
Contents of section .data:
4000 00000000 00000000 08400000 00000000 .........@......
4010 21000000 67341512 41424344 4500 !...g4..ABCDE.
objdump 도구를 활용하면 데이터 세그먼트의 내용을 확인할 수 있다. 위 출력 결과에서 .data 섹션 내용 첫 번째 열은 주소, 다음 4개의 열은 해당 주소의 내용(4바이트), 마지막 열은 해당 주소 내용의 아스키 표현이다. -s 옵션은 선택된 섹션의 모든 내용 표시, -j .data 옵션은 .data 섹션의 모든 내용을 표시하도록 명령한다.
리틀 엔디언(little-endian)이기 때문에 바이트가 거꾸로 배열되어 있는 것을 확인할 수 있다. 엔디언은 CPU의 속성이기 때문에 상이한 엔디언을 가진 하드웨어에서는 같은 실행 가능한 목적 파일을 실행할 수 없다.
텍스트 세그먼트 또는 코드 세그먼트는 프로그램의 모든 기계 수준 명령어를 저장한다. 이 명령어는 프로세서가 프로세스를 실행할 때 필요로 한다.
코드 박스 4-6 [예제 4-5] 최소의 C 프로그램
int main(int argc, char** argv) {
return 0;
}
셀 박스 4-11 objdump로 main 함수에 해당하는 섹션의 내용 확인하기
$ gcc 4_5.c -o 4_5.out
$ objdump -S 4_5.out
4_5.out: file format elf64-x86-64
Disassembly of section .init:
0000000000001000 <_init>:
108d: 74 09 je 1098 <deregister_tm_clones+0x28>
108f: ff e0 jmp *%rax
1091: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
1098: c3 ret
1099: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
00000000000010a0 <register_tm_clones>:
10a0: 48 8d 3d 69 2f 00 00 lea 0x2f69(%rip),%rdi # 4010 <__TMC_END__>
10a7: 48 8d 35 62 2f 00 00 lea 0x2f62(%rip),%rsi # 4010 <__TMC_END__>
10ae: 48 29 fe sub %rdi,%rsi
10b1: 48 89 f0 mov %rsi,%rax
10b4: 48 c1 ee 3f shr $0x3f,%rsi
10b8: 48 c1 f8 03 sar $0x3,%rax
10bc: 48 01 c6 add %rax,%rsi
10bf: 48 d1 fe sar $1,%rsi
10c2: 74 14 je 10d8 <register_tm_clones+0x38>
10c4: 48 8b 05 25 2f 00 00 mov 0x2f25(%rip),%rax # 3ff0 <_ITM_registerTMCloneTable>
10cb: 48 85 c0 test %rax,%rax
10ce: 74 08 je 10d8 <register_tm_clones+0x38>
10d0: ff e0 jmp *%rax
10d2: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
10d8: c3 ret
10d9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
00000000000010e0 <__do_global_dtors_aux>:
10e0: f3 0f 1e fa endbr64
10e4: 80 3d 25 2f 00 00 00 cmpb $0x0,0x2f25(%rip) # 4010 <__TMC_END__>
10eb: 75 2b jne 1118 <__do_global_dtors_aux+0x38>
10ed: 55 push %rbp
10ee: 48 83 3d 02 2f 00 00 cmpq $0x0,0x2f02(%rip) # 3ff8 <__cxa_finalize@GLIBC_2.2.5>
10f5: 00
10f6: 48 89 e5 mov %rsp,%rbp
10f9: 74 0c je 1107 <__do_global_dtors_aux+0x27>
10fb: 48 8b 3d 06 2f 00 00 mov 0x2f06(%rip),%rdi # 4008 <__dso_handle>
1102: e8 29 ff ff ff call 1030 <__cxa_finalize@plt>
1107: e8 64 ff ff ff call 1070 <deregister_tm_clones>
110c: c6 05 fd 2e 00 00 01 movb $0x1,0x2efd(%rip) # 4010 <__TMC_END__>
1113: 5d pop %rbp
1114: c3 ret
1115: 0f 1f 00 nopl (%rax)
1118: c3 ret
1119: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
0000000000001120 <frame_dummy>:
1120: f3 0f 1e fa endbr64
1124: e9 77 ff ff ff jmp 10a0 <register_tm_clones>
0000000000001129 <main>:
1129: f3 0f 1e fa endbr64
112d: 55 push %rbp
112e: 48 89 e5 mov %rsp,%rbp
1131: 89 7d fc mov %edi,-0x4(%rbp)
1134: 48 89 75 f0 mov %rsi,-0x10(%rbp)
1138: b8 00 00 00 00 mov $0x0,%eax
113d: 5d pop %rbp
113e: c3 ret
Disassembly of section .fini:
0000000000001140 <_fini>:
1140: f3 0f 1e fa endbr64
1144: 48 83 ec 08 sub $0x8,%rsp
1148: 48 83 c4 08 add $0x8,%rsp
114c: c3 ret
리눅스는 main 함수 실행 전후로 glibc 라이브러리에서 가져온 함수를 실행한다. objdump 도구로 확인한 텍스트 세그먼트의 모습을 통해 이를 확인할 수 있다.
동적 메모리 레이아웃은 프로세스의 런타임 메모리(runtime memory)이다. 실행 가능한 목적 파일 실행 시 로더(loader)가 호출한 프로그램은 프로세스 스폰, 초기 메모리 레이아웃(동적 메모리 레이아웃) 생성, 정적 레이아웃의 세그먼트 복제 등의 작업을 수행한다.
실행 중인 프로세스의 메모리 레이아웃에는 5개의 세그먼트가 존재한다. 3개는 실행 가능한 목적 파일에 있는 정적 레이아웃으로부터 복제된 것이고, 나머지 2개는 동적인 스택(stack), 힙(heap)이다. 프로세스의 동적 메모리 레이아웃은 5개의 세그먼트를 모두 포함해 구성된다.
코드 박스 4-7 [예제 4-6] 동적 메모리 레이아웃을 검사
#include <unistd.h>
int main(int argc, char** argv) {
// Infinite loop
while (1) {
sleep(1); // Sleep for 1 second
}
return 0;
}
셀 박스 4-12 리눅스에서 [예제 4-6] 컴파일하기
$ gcc 4_6.c -o 4_6.out
셀 박스 4-13 백그라운드에서 [예제 4-6] 실행하기
$ ./4_6.out &
[1] 7242
리눅스 머신에서 프로세스 정보는 /proc 경로에 있는 파일에서 찾을 수 있다. 이 정보는 procfs라는 특별한 파일 시스템을 사용한다. 프로세스의 메모리는 수많은 메모리 매핑(memory mapping)으로 구성되며, 각 메모리 매핑은 특정 파일이나 세그먼트에 매핑되는 메모리의 전용 영역을 나타낸다.
셀 박스 4-14 /proc/7242의 내용을 나열
$ ls -l /proc/7242
total 0
-r--r--r-- 1 yushin yushin 0 Aug 31 01:00 arch_status
dr-xr-xr-x 2 yushin yushin 0 Aug 31 01:00 attr
-rw-r--r-- 1 yushin yushin 0 Aug 31 01:00 autogroup
-r-------- 1 yushin yushin 0 Aug 31 01:00 auxv
-r--r--r-- 1 yushin yushin 0 Aug 31 01:00 cgroup
--w------- 1 yushin yushin 0 Aug 31 01:00 clear_refs
-r--r--r-- 1 yushin yushin 0 Aug 31 00:57 cmdline
-rw-r--r-- 1 yushin yushin 0 Aug 31 01:00 comm
-rw-r--r-- 1 yushin yushin 0 Aug 31 01:00 coredump_filter
-r--r--r-- 1 yushin yushin 0 Aug 31 01:00 cpu_resctrl_groups
lrwxrwxrwx 1 yushin yushin 0 Aug 31 01:00 cwd -> /home/yushin/Study/Study_C/ExtremeC/chapter04
-r-------- 1 yushin yushin 0 Aug 31 00:57 environ
lrwxrwxrwx 1 yushin yushin 0 Aug 31 01:00 exe -> /home/yushin/Study/Study_C/ExtremeC/chapter04/4_6.out
dr-x------ 2 yushin yushin 4 Aug 31 01:00 fd
dr-xr-xr-x 2 yushin yushin 0 Aug 31 01:00 fdinfo
-rw-r--r-- 1 yushin yushin 0 Aug 31 01:00 gid_map
-r-------- 1 yushin yushin 0 Aug 31 01:00 io
-r-------- 1 yushin yushin 0 Aug 31 01:00 ksm_merging_pages
-r-------- 1 yushin yushin 0 Aug 31 01:00 ksm_stat
-r--r--r-- 1 yushin yushin 0 Aug 31 01:00 latency
-r--r--r-- 1 yushin yushin 0 Aug 31 01:00 limits
-rw-r--r-- 1 yushin yushin 0 Aug 31 01:00 loginuid
dr-x------ 2 yushin yushin 0 Aug 31 01:00 map_files
-r--r--r-- 1 yushin yushin 0 Aug 31 01:00 maps
-rw------- 1 yushin yushin 0 Aug 31 01:00 mem
-r--r--r-- 1 yushin yushin 0 Aug 31 01:00 mountinfo
-r--r--r-- 1 yushin yushin 0 Aug 31 01:00 mounts
-r-------- 1 yushin yushin 0 Aug 31 01:00 mountstats
dr-xr-xr-x 56 yushin yushin 0 Aug 31 01:00 net
dr-x--x--x 2 yushin yushin 0 Aug 31 01:00 ns
-r--r--r-- 1 yushin yushin 0 Aug 31 01:00 numa_maps
-rw-r--r-- 1 yushin yushin 0 Aug 31 01:00 oom_adj
-r--r--r-- 1 yushin yushin 0 Aug 31 01:00 oom_score
-rw-r--r-- 1 yushin yushin 0 Aug 31 01:00 oom_score_adj
-r-------- 1 yushin yushin 0 Aug 31 01:00 pagemap
-r-------- 1 yushin yushin 0 Aug 31 01:00 patch_state
-r-------- 1 yushin yushin 0 Aug 31 01:00 personality
-rw-r--r-- 1 yushin yushin 0 Aug 31 01:00 projid_map
lrwxrwxrwx 1 yushin yushin 0 Aug 31 01:00 root -> /
-rw-r--r-- 1 yushin yushin 0 Aug 31 01:00 sched
-r--r--r-- 1 yushin yushin 0 Aug 31 01:00 schedstat
-r--r--r-- 1 yushin yushin 0 Aug 31 01:00 sessionid
-rw-r--r-- 1 yushin yushin 0 Aug 31 01:00 setgroups
-r--r--r-- 1 yushin yushin 0 Aug 31 01:00 smaps
-r--r--r-- 1 yushin yushin 0 Aug 31 01:00 smaps_rollup
-r-------- 1 yushin yushin 0 Aug 31 01:00 stack
-r--r--r-- 1 yushin yushin 0 Aug 31 00:57 stat
-r--r--r-- 1 yushin yushin 0 Aug 31 01:00 statm
-r--r--r-- 1 yushin yushin 0 Aug 31 00:57 status
-r-------- 1 yushin yushin 0 Aug 31 01:00 syscall
dr-xr-xr-x 3 yushin yushin 0 Aug 31 01:00 task
-rw-r--r-- 1 yushin yushin 0 Aug 31 01:00 timens_offsets
-r--r--r-- 1 yushin yushin 0 Aug 31 01:00 timers
-rw-rw-rw- 1 yushin yushin 0 Aug 31 01:00 timerslack_ns
-rw-r--r-- 1 yushin yushin 0 Aug 31 01:00 uid_map
-r--r--r-- 1 yushin yushin 0 Aug 31 01:00 wchan
프로세스의 메모리 매핑을 쿼리하려면 maps 파일의 내용을 확인해야 한다.
셀 박스 4-15 /proc/7242/maps의 내용을 덤프하기
$ cat /proc/7242/maps
5e4796a37000-5e4796a38000 r--p 00000000 103:04 20730542 /home/yushin/Study/Study_C/ExtremeC/chapter04/4_6.out
5e4796a38000-5e4796a39000 r-xp 00001000 103:04 20730542 /home/yushin/Study/Study_C/ExtremeC/chapter04/4_6.out
5e4796a39000-5e4796a3a000 r--p 00002000 103:04 20730542 /home/yushin/Study/Study_C/ExtremeC/chapter04/4_6.out
5e4796a3a000-5e4796a3b000 r--p 00002000 103:04 20730542 /home/yushin/Study/Study_C/ExtremeC/chapter04/4_6.out
5e4796a3b000-5e4796a3c000 rw-p 00003000 103:04 20730542 /home/yushin/Study/Study_C/ExtremeC/chapter04/4_6.out
7676c5600000-7676c5628000 r--p 00000000 103:04 3934850 /usr/lib/x86_64-linux-gnu/libc.so.6
7676c5628000-7676c57c0000 r-xp 00028000 103:04 3934850 /usr/lib/x86_64-linux-gnu/libc.so.6
7676c57c0000-7676c580e000 r--p 001c0000 103:04 3934850 /usr/lib/x86_64-linux-gnu/libc.so.6
7676c580e000-7676c5812000 r--p 0020d000 103:04 3934850 /usr/lib/x86_64-linux-gnu/libc.so.6
7676c5812000-7676c5814000 rw-p 00211000 103:04 3934850 /usr/lib/x86_64-linux-gnu/libc.so.6
7676c5814000-7676c5821000 rw-p 00000000 00:00 0
7676c582d000-7676c5830000 rw-p 00000000 00:00 0
7676c5842000-7676c5844000 rw-p 00000000 00:00 0
7676c5844000-7676c5848000 r--p 00000000 00:00 0 [vvar]
7676c5848000-7676c584a000 r--p 00000000 00:00 0 [vvar_vclock]
7676c584a000-7676c584c000 r-xp 00000000 00:00 0 [vdso]
7676c584c000-7676c584d000 r--p 00000000 103:04 3934847 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7676c584d000-7676c587c000 r-xp 00001000 103:04 3934847 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7676c587c000-7676c5887000 r--p 00030000 103:04 3934847 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7676c5887000-7676c5889000 r--p 0003b000 103:04 3934847 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7676c5889000-7676c588a000 rw-p 0003d000 103:04 3934847 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7676c588a000-7676c588b000 rw-p 00000000 00:00 0
7ffcb2013000-7ffcb2035000 rw-p 00000000 00:00 0 [stack]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]
덤프에서 각 열은 순서대로 다음을 의미한다.
스택 세그먼트에는 지역 변수, 함수 호출 정보와 같은 스택 프레임이 저장된다. 대체로 크기가 제한되기 때문에 큰 객체를 저장하기에 적합하지 않고, 가득 차면 스택 오버플로가 발생하면서 프로세스가 운영체제에 의해 종료된다.
코드 박스 4-8 스택 세그먼트에서 할당된 메모리를 갖는 지역 변수 선언하기
void func() {
// The memory space needed for the next variable will be allocated from the stack segment.
int a;
...
}
변수 선언 시 변수의 할당 위치를 컴파일러에 지시하지 않는 한 기본적으로 스택 세그먼트가 사용된다.
함수 호출 시마다 스택 프레임(stack frame)이 호출한 새 엔트리가 스택 세그먼트의 최상단에 놓인다. 스택의 크기는 제한되므로 작은 변수를 선언하고, 많은 재귀(recursive) 호출을 지양하는 것이 좋다.
스택 세그먼트는 전용 데이터(private data)를 포함하며 민감할 수 있다. 또한 해당 프로세스 전용이므로 디버거 프로그램을 활용해 해당 프로세스의 관점에서 확인해야 한다.
코드 박스 4-9 [예제 4-7] 힙 세그먼트를 검사
#include <unistd.h> // header for sleep function
#include <stdlib.h> // header for malloc function
#include <stdio.h> // header for printf function
int main(int argc, char** argv) {
void* ptr = malloc(1024); // allocate 1KB from heap
printf("Address: %p\n", ptr);
fflush(stdout);
// infinite loop
while (1) {
sleep(1); // sleep for 1 second
}
return 0;
}
malloc 함수는 힙 세그먼트에 추가 메모리를 할당하는 기본 방식이다. 이 함수는 할당되어야 하는 바이트 수를 인수로 받아 제네릭 포인터로 반환한다. 그러므로 형 변환 없이 역참조(dereference)될 수 없다.
셀 박스 4-16 [예제 4-7]을 컴파일하고 실행하기
$ g++ 4_7.c -o 4_7.out
$ ./4_7.out &
[1] 28805
Address: 0x61da50cca010
셀 박스 4-17 /proc/28805/maps의 내용을 덤프하기
$ cat /proc/28805/maps
61da49867000-61da49868000 r--p 00000000 08:30 130982 /home/yush1nk1m/Study/Study_C/ExtremeC/chapter04/4_7.out
61da49868000-61da49869000 r-xp 00001000 08:30 130982 /home/yush1nk1m/Study/Study_C/ExtremeC/chapter04/4_7.out
61da49869000-61da4986a000 r--p 00002000 08:30 130982 /home/yush1nk1m/Study/Study_C/ExtremeC/chapter04/4_7.out
61da4986a000-61da4986b000 r--p 00002000 08:30 130982 /home/yush1nk1m/Study/Study_C/ExtremeC/chapter04/4_7.out
61da4986b000-61da4986c000 rw-p 00003000 08:30 130982 /home/yush1nk1m/Study/Study_C/ExtremeC/chapter04/4_7.out
61da50cca000-61da50ceb000 rw-p 00000000 00:00 0 [heap]
7ad982e00000-7ad982e28000 r--p 00000000 08:30 13647 /usr/lib/x86_64-linux-gnu/libc.so.6
7ad982e28000-7ad982fc0000 r-xp 00028000 08:30 13647 /usr/lib/x86_64-linux-gnu/libc.so.6
7ad982fc0000-7ad98300e000 r--p 001c0000 08:30 13647 /usr/lib/x86_64-linux-gnu/libc.so.6
7ad98300e000-7ad983012000 r--p 0020d000 08:30 13647 /usr/lib/x86_64-linux-gnu/libc.so.6
7ad983012000-7ad983014000 rw-p 00211000 08:30 13647 /usr/lib/x86_64-linux-gnu/libc.so.6
7ad983014000-7ad983021000 rw-p 00000000 00:00 0
7ad98317f000-7ad983182000 rw-p 00000000 00:00 0
7ad98318a000-7ad98318c000 rw-p 00000000 00:00 0
7ad98318c000-7ad983190000 r--p 00000000 00:00 0 [vvar]
7ad983190000-7ad983192000 r--p 00000000 00:00 0 [vvar_vclock]
7ad983192000-7ad983194000 r-xp 00000000 00:00 0 [vdso]
7ad983194000-7ad983195000 r--p 00000000 08:30 13644 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7ad983195000-7ad9831c4000 r-xp 00001000 08:30 13644 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7ad9831c4000-7ad9831cf000 r--p 00030000 08:30 13644 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7ad9831cf000-7ad9831d1000 r--p 0003b000 08:30 13644 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7ad9831d1000-7ad9831d2000 rw-p 0003d000 08:30 13644 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7ad9831d2000-7ad9831d3000 rw-p 00000000 00:00 0
7ffd6fa6e000-7ffd6fa90000 rw-p 00000000 00:00 0 [stack]
1KB만 할당하였음에도 [heap]에 해당하는 힙 공간은 132KB가 할당되었음을 확인할 수 있다. 이는 힙 세그먼트에서 메모리 할당에 드는 비용이 크기 때문에 malloc 함수 호출 시마다 메모리를 추가로 할당하지 않고자 수행되는 동작이다.
또한, 힙 세그먼트의 시작 주소는 0x61da50cca000이지만 malloc 함수에 의해 반환된 포인터는 0x61da50cca010 주소로 힙 세그먼트 시작 지점으로부터 16바이트 떨어진 지점을 가리키고 있다.
코드 박스 4-10 [예제 4-8] 힙 메모리와 상호작용하는 포인터 사용하기
#include <stdio.h> // header for printf function
#include <stdlib.h> // header for malloc & free functions
void fill(char* ptr) {
ptr[0] = 'H';
ptr[1] = 'e';
ptr[2] = 'l';
ptr[3] = 'l';
ptr[4] = 'o';
}
int main(int argc, char** argv) {
void* gptr = malloc(10 * sizeof(char));
char* ptr = (char*) gptr;
fill(ptr);
printf("%s!\n", ptr);
free(ptr);
return 0;
}
힙 메모리는 프로그램 또는 개발자가 메모리 할당을 담당한다. 프로그램은 메모리가 더 이상 필요하지 않을 때 해제를 담당하기도 한다. 접근 불가능한 힙 메모리를 할당받는 것은 메모리 누수(memory leakage)로 간주된다.