<aside> 💡 오늘은 어셈블리 수준에서 메모리에 어떤 일이 일어나는지 확인해 보자. C언어는 어셈블리어와 매우 밀접한 관계를 가진 언어이기 때문에, 알 필요가 있다고 생각한다. 여기로 건너뛰어서 보기 시작해도 좋다.

</aside>

objdmp와 clang의 -S플래그로 컴파일한 disassembly와 assembly다.

Apple M2 프로세서, 8GB 메모리이고, macOS Ventura 13.4.1이다.

Linux는 같은 컴퓨터의 Asahi Linux(Arch Linux ARM), Raspberry Pi 4B Ubuntu 22.04, 그리고 x86_64 프로세서인 Ryzen 3600X와 16GB 메모리, Ubuntu 22.04에서 실험하였다.

macOS: Mach의 가상 메모리 구조

이 부분은 MacOS의 Darwin의 XNU의 Mach 커널, aarch64 기준으로 설명된 부분이다.

가상 메모리는 text, data, bss, stack, images, heap 의 구조로 되어있다.

macOS 현재 시스템 페이지 사이즈는 pagesize 명령으로 확인할 수 있는데, 16384 바이트 = 16KiB = 0x4000바이트이다.

참고로 프로세서의 adrp 명령에서는 1페이지 = 4096 바이트 = 4KiB = 0x1000로 책정된다.

세부 주소와 스택과 힙의 크기는 컴퓨터와 상황마다 다르다.

세부 주소와 스택과 힙의 크기는 컴퓨터와 상황마다 다르다.

프로그램과 Mach-O 헤더들은 0페이지(0~3FFF)에 저장된다. 실제 가상 메모리 주소는 0x1_0000_3000인데, 이하 0x3000으로 생략하겠다.

프로그램은 이 0페이지의 아래쪽 부분에 정렬되어 저장된다.

const char * 를 선언하면, 가리키는 값은 text 부분에 들어간다.

data에는 프로그램 내내 살아있는(static duration), 초기화된 녀석들이 들어간다.

bss에는 프로그램 내내 살아있지만, 초기화되지 않은 녀석들이 들어간다. C, C++에서는 static 변수들은 0으로 초기화해 주는데, 디테일은 아래에서 설명하겠다.

스택은 높은 주소에서 낮은 주소로 쌓인다.

sp(stack pointer) 가 스택이 어디까지인지를 가리킨다. 스택 프레임이 만들어지면, fp(frame pointer, x29)와 lr(link register, x30)값을 저장하고, 현재 fp가 저장된 fp를 가리키게 한다.

스택의 로컬 변수도 아래에서 위로 쌓인다. 실제 push pop되는 것은 변수들이 아니라 스택 프레임이다.

중간 부분에 커널의 dynamic linker와 라이브러리이미지들이 들어있다. <>로 감싸진 헤더들은 #include하면 text 부분에 통째로 복사되지 않는다. text에는 함수 호출이, data의 got(Global Offsets Table)부분에 포인터 정보가 들어있고, 그 포인터가 이 라이브러리에 있는 실제 함수를 가리킨다. 이는 다이나믹 링크되는 Mach의 특성이다.

힙은 낮은 주소에서 높은 주소로 늘어난다.

malloc을 사용하면 16바이트 단위로 물어오게 된다. 4바이트를 alloc해도 16바이트를 물어온다. 또 malloc을 하면 다음 0x4000 뒤의 위치에 지정된다.

메모리 전체 덤프를 떠 볼수 있다.

vmmap 덤프 (스압주의)

Linux의 메모리 구조

Screenshot 2023-07-13 at 16.14.04.png

참고: 방금 그림과는 다르게 이 그림은 위가 높은 주소, 아래가 낮은 주소임.

스택과 힙 위치만 다르고 나머지는 같다.

aarch64, 16kb(0x4000) 페이지 사이즈 기준으로, lldb로 메모리를 들여다 보았다.

스택은 0xffff’ffff’f0a0 쯤부터 시작하고, 0xaaaa’aaab’42a0 쯤이다. 텍스트에서 5페이지 정도 떨어진 부분이다. 텍스트 시작부분은 0xaaaa’aaaa’0000 쯤에 있다.

C언어에서 각 부분별로 알아야 할 것들

지금까지 메모리의 구조에 대해서 알아보았는데, 이게 무엇이고 이걸 왜 알아야 할까?

data 부분과 bss 부분에 대하여