c++ 컴파일 표준과 구현체
2025년 10월 10일
참고 #
- itanium 문서
- msvc abi 공식문서는 없지만, 여러 문서를 참고할 수 있음
- c++ 표준 abi 문서
C++ 컴파일 과정 #
C++로 작성된 소스코드는 운영체제에 설치된 컴파일러에 의해 기계어로 번역된다. 이 번역(컴파일) 과정에서 운영체제마다 다른 복잡한 코드가 추가되며 운영체제(CPU)에서 실행할 수 있는 실행파일로 만들어지는 것인데, 대충 듣기만 해도 운영체제에 아주 종속적일 것 같아 보인다.
컴파일 과정을 살펴보면 마지막에 링커에 의해 runtime lib 이 포함되는 것을 볼 수 있는데, 이 runtime이 링킹과정에서 entrypoint가 되고 main함수 이전에 initarray 호출, TLS영역 초기화 등의 작업을 한 뒤 main을 호출하는 역할을 하거나 예외처리 함수가 구현되어 있는 라이브러리이다.
목적파일 #
목적파일은 이미 ELF 포맷이다. 예외처리 코드를 작성하고 objdump로 main 함수를 확인하면 1f: 위치에 call 24 명령이 보이는데, 사실 20: 00000000 으로 채워져 있어서 다음 명령 주소로 해석된 것일 뿐이다.
링킹과정에서 20: 바이트부터 4byte 크기만큼 __cxa_allocate_exception 함수 주소로 패치하라는 의미이기 때문에 결국 runtime lib의 예외처리 함수를 호출하게 된다.
컴파일러가 내부적으로 운영체제(플랫폼 ABI + Cpp ABI)에 맞게 구현되어 있기 때문에 runtime lib에 맞게 컴파일을 한다는 의미이다.
1kdh@DESKTOP-MHEA7GE:~$ cat main2.cpp
2int main() {
3 int i = 0;
4 try {
5 i = 10;
6 throw 20;
7 } catch(int e) {
8 i = 30;
9 }
10}
11
12kdh@DESKTOP-MHEA7GE:~$ g++ -c -O0 main2.cpp
13
14kdh@DESKTOP-MHEA7GE:~$ readelf -S main2.o
15 [Nr] Name Type Address Offset
16 [ 3] .text PROGBITS 0000000000000000 00000058
17
18kdh@DESKTOP-MHEA7GE:~$ objdump -dr main2.o
19main2.o: file format elf64-x86-64
20Disassembly of section .text: (텍스트 섹션은 0x58 위치임)
210000000000000000 <main>:
22 0: f3 0f 1e fa endbr64
23 4: 55 push %rbp
24 5: 48 89 e5 mov %rsp,%rbp
25 8: 48 83 ec 10 sub $0x10,%rsp
26 c: c7 45 f8 00 00 00 00 movl $0x0,-0x8(%rbp)
27 13: c7 45 f8 0a 00 00 00 movl $0xa,-0x8(%rbp)
28 1a: bf 04 00 00 00 mov $0x4,%edi
29 1f: e8 00 00 00 00 call 24 <main+0x24>
30 20: R_X86_64_PLT32 __cxa_allocate_exception-0x4
31 24: c7 00 14 00 00 00 movl $0x14,(%rax)
32 2a: ba 00 00 00 00 mov $0x0,%edx
33 2f: 48 8d 0d 00 00 00 00 lea 0x0(%rip),%rcx # 36 <main+0x36>
34 32: R_X86_64_PC32 _ZTIi-0x4
35 36: 48 89 ce mov %rcx,%rsi
36 39: 48 89 c7 mov %rax,%rdi
37 3c: e8 00 00 00 00 call 41 <main+0x41>
38 3d: R_X86_64_PLT32 __cxa_throw-0x4
39 41: f3 0f 1e fa endbr64
40 45: 48 83 fa 01 cmp $0x1,%rdx
41 49: 74 08 je 53 <main+0x53>
42 4b: 48 89 c7 mov %rax,%rdi
43 4e: e8 00 00 00 00 call 53 <main+0x53>
44 4f: R_X86_64_PLT32 _Unwind_Resume-0x4
45 53: 48 89 c7 mov %rax,%rdi
46 56: e8 00 00 00 00 call 5b <main+0x5b>
47 57: R_X86_64_PLT32 __cxa_begin_catch-0x4
48 5b: 8b 00 mov (%rax),%eax
49 5d: 89 45 fc mov %eax,-0x4(%rbp)
50 60: c7 45 f8 1e 00 00 00 movl $0x1e,-0x8(%rbp)
51 67: e8 00 00 00 00 call 6c <main+0x6c>
52 68: R_X86_64_PLT32 __cxa_end_catch-0x4
53 6c: b8 00 00 00 00 mov $0x0,%eax
54 71: c9 leave
55 72: c3 ret
링킹과정 #
ldd 로 링킹된 라이브러리들을 확인할 수 있다.
- libstdc++.so.6: Cpp runtime + STL
- libgcc_s.so.1: unwinder + gcc support runtime
- libc.so.6: libc + C runtime
- lib64/ld-linux-x86-64.so.2: 동적로더(ld)
1kdh@DESKTOP-MHEA7GE:~$ g++ main2.cpp
2kdh@DESKTOP-MHEA7GE:~$ ldd a.out
3 linux-vdso.so.1 (0x00007ffc83d03000)
4 libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fe7e4ec5000)
5 libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fe7e4ea5000)
6 libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fe7e4c7c000)
7 libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fe7e4b95000)
8 /lib64/ld-linux-x86-64.so.2 (0x00007fe7e5101000)
엔트리포인트는 _start 라는 함수인 것을 확인할 수 있고, 11f8: 에서 main 함수를 인자로 넘겨서 __libc_start_main 함수를 호출하는 것을 확인할 수 있다.
이 함수 안에서 CRT가 프로세스 초기화나 initarray 실행 후 main을 호출해준다.
main 함수도 확인해보면 오브젝트 파일일 때 bf 04 00 00 00 e8 00 00 00 00 바이트 패턴이였던 위치가 예외처리용 함수 주소로 패치된 것을 확인할 수 있다.
1kdh@DESKTOP-MHEA7GE:~$ readelf -a a.out
2 Entry point address: 0x10e0
3
4kdh@DESKTOP-MHEA7GE:~$ objdump -d a.out
500000000000010e0 <_start>:
6 10e0: f3 0f 1e fa endbr64
7 10e4: 31 ed xor %ebp,%ebp
8 10e6: 49 89 d1 mov %rdx,%r9
9 10e9: 5e pop %rsi
10 10ea: 48 89 e2 mov %rsp,%rdx
11 10ed: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp
12 10f1: 50 push %rax
13 10f2: 54 push %rsp
14 10f3: 45 31 c0 xor %r8d,%r8d
15 10f6: 31 c9 xor %ecx,%ecx
16 10f8: 48 8d 3d ca 00 00 00 lea 0xca(%rip),%rdi # 11c9 <main>
17 10ff: ff 15 db 2e 00 00 call *0x2edb(%rip) # 3fe0 <__libc_start_main@GLIBC_2.34>
18 1105: f4 hlt
19 1106: 66 2e 0f 1f 84 00 00 cs nopw 0x0(%rax,%rax,1)
20 110d: 00 00 00
21
2200000000000011c9 <main>:
23 11c9: f3 0f 1e fa endbr64
24 11cd: 55 push %rbp
25 11ce: 48 89 e5 mov %rsp,%rbp
26 11d1: 48 83 ec 10 sub $0x10,%rsp
27 11d5: c7 45 f8 00 00 00 00 movl $0x0,-0x8(%rbp)
28 11dc: c7 45 f8 0a 00 00 00 movl $0xa,-0x8(%rbp)
29 11e3: bf 04 00 00 00 mov $0x4,%edi
30 11e8: e8 b3 fe ff ff call 10a0 <__cxa_allocate_exception@plt>
31 ...
freestanding 컴파일 #
옵션을 따로 지정하지 않고 컴파일했을때 기본값으로 hosted 방식 컴파일이 실행된다. 이 방식은 host(운영체제)의 환경에서 실행되는 것을 가정하고 컴파일되기 때문에 이미 빌드된 C/C++ runtime, libc, STL이 사용된다. 그렇기 때문에 따로 구현하지 않아도 예외처리 핸들러나 _start 함수가 생겨난 것이다.
freestanding 컴파일옵션을 주고, exception이나 rtti 같은 crt 연관된 작업을 전부 빼면 _start 함수 없이 컴파일되며, hosted 컴파일시 링킹됐던 기본 라이브러리들이 빠져있는 것을 볼 수 있다.
1kdh@DESKTOP-MHEA7GE:~$ g++ -ffreestanding -fno-exceptions -fno-rtti -nostdlib main2.cpp
2/usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000000001000
3
4kdh@DESKTOP-MHEA7GE:~$ readelf -a a.out
5 Entry point address: 0x1000
6
7kdh@DESKTOP-MHEA7GE:~$ ldd a.out
8 statically linked
9
10kdh@DESKTOP-MHEA7GE:~$ objdump -dr a.out
11Disassembly of section .text:
120000000000001000 <main>:
13 1000: f3 0f 1e fa endbr64
14 1004: 55 push %rbp
ABI? #
C++ 프로그램은 Hosted 컴파일 과정에서 컴파일 옵션이나 운영체제 환경(ABI)에 맞춰서 오브젝트 파일을 만들고, 링크 시 라이브러리들을 연결한다.
실행할때 운영체제의 로더가 파일 포맷에 맞게 메모리에 적재하고 entrypoint 를 호출한다.
ABI(Application Binary Interface)는 작성한 소스코드를 바이너리로 어떻게 표현할지에 대한 규약을 의미하며, 운영체제, CPU아키텍쳐, 컴파일러의 조합에 따라 수많은 ABI로 표현된다.
플랫폼 ABI는 System V ABI, Microsoft x64 ABI, AAPCS64(Android) 정도가 있고, C++ ABI는 Itanium, MSVC 등이 있다.
컴파일 프론트엔드 (파서/AST → IR) #
전처리 단계에서는 C++문법이 해석되지 않기 떄문에 ABI와 관련이 없지만, 컴파일 프론트엔드 단계에서는 C++ ABI가 개입하게 된다.
C++ ABI가 하는 일 #
- 네임 맹글링
각각의 함수를 링커가 식별할 수 있도록 문자열 형태의 이름(심볼)으로 변경한다. 템플릿/오버로드/네임스페이스 정보들을 인코딩하는 규칙이 네임맹글링이다. ex)int MyClass::getValue(int)→_ZN7MyClass8getValueEi - 클래스 레이아웃 결정
멤버의 배치(메모리상 오프셋, 정렬)나 패딩, 서브오브젝트의 오프셋을 결정한다.1kdh@DESKTOP-MHEA7GE:~/test$ clang++ -std=c++17 -c ex.cpp \ 2 -Xclang -fdump-record-layouts -o /dev/null 3 4*** Dumping AST Record Layout 5 0 | struct Derived 6 0 | struct Base (primary base) 7 0 | (Base vtable pointer) 8 8 | int b 9 12 | int d 10 | [sizeof=16, dsize=16, align=8, 11 | nvsize=16, nvalign=8] - RTTI에 대한 정보
RTTI(type_info) 객체는 dynamic_cast 등 런타임에 객체의 타입정보를 담아두는 공간인데, 동일한 클래스라면 동일한 RTTI를 가리키게된다.
이 RTTI 객체는 프로세스안에서 유일해야 하기 때문에 프론트엔드 단계에서는 레이아웃이나 이름, 속성을 IR로 지정해둔 뒤, 링킹과정에서 같은 메모리주소를 가리키도록 병합한다.1kdh@DESKTOP-MHEA7GE:~/test$ clang++ -std=c++17 -S -emit-llvm ex.cpp -o ex.ll 2# RTTI 객체의 레이아웃. ZTI는 typeinfo 객체를 의미 3# 8byte 3개. dso_local은 링커에서 처리한다는 의미 4@_ZTI7Derived = dso_local constant { i8*, i8*, i8* } { 5 i8* bitcast (i8** getelementptr inbounds (i8*, i8** @_ZTVN10__cxxabiv120__si_class_type_infoE, i64 2) to i8*), 6 i8* getelementptr inbounds ([9 x i8], [9 x i8]* @_ZTS7Derived, i32 0, i32 0), 7 i8* bitcast ({ i8*, i8* }* @_ZTI4Base to i8*) 8}, align 8 - 예외경로 코드 형태 결정
소스코드의try-catch구문과 소멸자를 가진 객체가 있는지 분석해서 예외 발생 시cleanup/catch정보를 IR로 작성한다.
컴파일 백엔드 (IR → 목적파일) #
여기에서는 실제 바이너리 코드가 작성되기 때문에 플랫폼 ABI가 개입된다.
플랫폼 ABI가 하는 일 #
- 함수 호출규약
플랫폼 ABI마다 함수 호출규약이 달라서 호출 시 어떤 레지스터나 스택으로 인자를 전달하는지, 반환값은 어떻게 반환하는지 등이 다르다.
플랫폼에서 호출 규약이 다른 함수를 호출한다면 인자가 제대로 전달되지 않아서 명령어 자체가 틀어지기 떄문에 예상하지 못한일이 발생할 수 있다.
헤더에 호출 규약을 적어두는 경우엔 컴파일 과정에서 해당 함수를 호출할때 호출규약에 맞게 바이너리를 구성해준다. - 스택 프레임 레이아웃 결정
스택프레임 관련 기능들이 다르기 때문에 정해두고 바이너리를 구성한다.- System V
- call 직전 16byte 정렬 상태여야 한다. call 이후 리턴주소로 인해 8byte 밀리는데 sub 명령으로 16byte가 정렬되게 한다.
RBX,RBP,R12–R15레지스터는 callee-saved 라서 callee에서 사용될때 저장하고 나머지는 caller-saved 이다.
둘다 컴파일러가 알아서 저장해주기 때문에 어셈블리 코드를 작성할때만 주의하면 된다.- red-zone : 간단한 leaf 함수는 프롤로그에서 rsp를 수정하지 않고 그냥
rsp-128 ~ rsp-1위치를 로컬변수 공간으로 사용해도된다.
- Win64
- 16byte 정렬상태여야 하는것은 동일하다.
RBX,RBP,RDI,RSI,R12–R15,XMM6-XMM15레지스터가 callee-saved 로, System V와 다르다.- shadow-space : caller가 callee를 위해 call 직전에 rsp를 조작해서 32byte 스택을 할당하고 레지스터로 전달된 인자의 백업을 담당한다.
- AArch64
- SP는 16바이트 정렬 유지
x19-x28,x29(fp),v8-v15레지스터가 callee-saved- 링크레지스터(x30)에 리턴주소를 넣고 함수를 호출한다. 프롤로그에서 x29(fp)와 x30(lr)을 스택에 저장한다.
- redzone이나 shadow-space같은 건 없다
- System V
- 예외처리 포맷 규칙 결정 및 메타데이터 저장
함수가 정상리턴 된다면 callee-saved 레지스터와 스택을 원복하면서 리턴주소로 점프하면되지만, 예외는 코드 중간에 발생하기 때문에 원복이 불가능하다.
catch를 만날때까지 각 스택프레임을 unwind 하면서 중간에 생성된 소멸자를 정확히 호출하는 등의 작업이 필요한데 이때 사용되는 정보들이 언와인드 메타데이터라고 한다.
이런 메타데이터를 컴파일타임에 전부 분석해서 ABI 마다 정해진 포맷과 위치에 저장해둔다.- 언와인드 테이블: 함수의 프롤로그, 에필로그가 스택이나 레지스터를 어떻게 변화해왔는지 변화 지점을 기록하고 예외 시 착지할
landing pad위치를 기록한다. - System V는 DWARF 방식으로 기록하고, Windows는 자체 Unwind Code 형식으로 기록한다.
- 함수 내의 모든
push,pop,subrsp등을 분석해서 런타임에 정확한 unwind가 가능하도록 기록한다.
- 언와인드 테이블: 함수의 프롤로그, 에필로그가 스택이나 레지스터를 어떻게 변화해왔는지 변화 지점을 기록하고 예외 시 착지할
- 심볼테이블, 섹션, 재배치 규칙 정의
- 함수의 심볼과 주소를 기록한 테이블을 기록한다.
- 각 섹션에 맞게 데이터를 분류해서 위치시킨다. (.text, .rodata, .bss 등)
- 링킹 과정에서 재배치할 정보들을 기록해둔다. (목적파일 안에서 확인했던 예외처리 함수처럼)
C++ ABI가 하는 일 #
프론트엔드가 vtable, RTTI 등의 구조를 결정하여 IR로 남기면, 백엔드는 그 IR을 보고 실제 바이너리 데이터(.rodata 섹션에 들어갈 vtable의 바이트 배열)를 생성한다.
링킹단계 #
C++ ABI 스펙을 구현한 런타임이 연결되며
C++ ABI #
- 네임 맹글링
- 가상함수테이블 레이아웃
- 예외처리
- 클래스와 객체 메모리 레이아웃
- RTTI
C++은 ISO에서 표준을 정의한것
우리가 어플리케이션을 만들 때 C/C++ 문법과 함께 라이브러리 함수를 호출하는 방식으로 구현하게 된다. 각 소스코드는 전처리 및 컴파일되어 목적파일이되고, 링킹과정에서 하나로 묶여 실행파일이 된다.
컴파일 과정에서는 문법들이 컴파일러에 의해 CPU를 직접 조작할 수 있는 기계어 코드로 변환된다.
운영체제마다 다를 수 있는 부분
- CPU 아키텍쳐 ABI
CPU, 레지스터, 호출규약, 정수/포인터 데이터모델(LP64/LLP64) - C++ ABI (MSVC, Itanium)
네임맹글링, 객체레이아웃(vtable, vptr, 가상상속), RTTI 구조, C++ 예외 런타임 인터페이스 - 플랫폼 ABI (오브젝트, 링커)
실행파일포맷, 재배치/심볼, 가시성 버저닝, 로더규칙,TLS모델 - 런타임라이브러리
CRuntime, libc, C++ 표준라이브러리(libstdc++/libc++/msvcp), 언와인더, new/delete 구현체 - 툴체인 옵션: 예외/RTTI on/off, 패킹/얼라인, LTO, visibility, dual ABI(_GLIBCXX_USE_CXX11_ABI) 등
예외 unwind 방식 C++ ABI가 런타임 인터페이스를 정하고, 언와인드 포맷은 플랫폼 아키텍쳐에서 좌우됨
모듈 경계 수칙: 예외/RTTI/new·delete/STL 컨테이너 경계 넘기지 않기, C API로 안정화 각 구현체마다 내부 구조가 다르기 때문에 넘기면안됨
대충 구조는 아래와 같다. 커널단에서 시스템콜을 구현해두고, libc은 시스템콜을 조작해서 C 표준에 맞게 구현하는게 목표이다.
시스템콜의 실제 구현
유저모드에서 syscall이라는 명령어를 호출하면 커널모드로 전환되며 내부에 구현된 시스템콜의 실제 구현부를 호출하게된다.
libc: ISO C, POSIX와 UCRT(Windows Universal CRT) 등이 있는데, 운영체제마다 필요로하는 함수가 다를 수 있기 때문에 인터페이스나 제공되는 API들이 다를 수 있다. (실제구현은 내부 시스템콜 조합이라 당연히 다름)
기본적으로 ISO C(ANSI C)가 표준이고, POSIX는 유닉스시스템에서 표준으로 제공되는 확장함수(멀티스레딩, 파일시스템조작 등)가 포함된다.
newlib은 POSIX 인터페이스 껍데기만 작성된 라이브러리이다. 실제 구현부는 운영체제에 맞게 직접 구현
CRT는 CRuntime으로 전역/정적객체 초기화/소멸, TLS 초기화 등 프로그램이 실행될 수 있는 런타임 환경을 제공해주는 부트스트랩코드임
libc에서 CRT까지 구현되기 때문에 합쳐서 이야기하는 경우도 많다.
1_start 함수: 프로그램의 실제 진입점(Entry Point)입니다. 커널이 프로그램을 실행하면 가장 먼저 이 함수가 호출됩니다.
2전역/정적 변수 초기화: main 함수가 호출되기 전에 전역 변수와 정적(static) 변수를 초기화합니다.
3main 함수 호출: 명령행 인자(argc, argv) 등을 설정하여 main 함수를 호출합니다.
4TLS (Thread-Local Storage) 초기화: 스레드별로 독립적인 저장 공간을 설정합니다.
5종료 처리: main 함수가 반환되면, 반환 값을 받아 프로그램을 정상적으로 종료시키는 역할을 합니다.
C++Runtime
1new/delete 연산자: 동적 메모리 할당 및 해제를 처리합니다. 내부적으로는 C의 malloc/free를 호출할 수 있습니다.
2전역/정적 객체의 생성자 및 소멸자 호출: CRT가 메모리만 할당해둔다면, C++ 런타임은 해당 메모리 위에서 객체의 생성자와 소멸자를 호출하여 객체를 완성하고 소멸시킵니다.
3예외 처리 (Exception Handling): try, catch, throw 키워드를 이용한 예외 발생 및 처리(unwinding) 메커니즘을 지원합니다.
4RTTI (Run-Time Type Information): dynamic_cast나 typeid 연산자처럼 프로그램 실행 중에 객체의 타입을 식별하는 기능을 제공합니다.
5C++ ABI (Application Binary Interface): 컴파일된 C++ 코드(함수 이름 맹글링, vtable 구조 등)가 서로 호환되도록 하는 규칙을 지원합니다. 말씀하신 Itanium ABI(GCC, Clang 등)와 MSVC ABI가 대표적입니다.
STL
1C++ 표준 라이브러리의 일부로, C++ 런타임이 제공하는 기능 외에 프로그래밍에 필요한 자료구조와 알고리즘을 모아놓은 것입니다.
2주요 구성 요소:
3컨테이너 (Containers): 데이터를 저장하는 객체 (vector, list, map, set 등).
4알고리즘 (Algorithms): 데이터를 처리하는 함수 (sort, find, copy 등).
5반복자 (Iterators): 컨테이너의 원소를 순회하고 접근하는 객체.
6함수 객체 (Functors): 함수처럼 동작하는 객체.
| 항목 | Linux x86_64 | Android x86_64 | Linux AArch64 | Android AArch64 | Linux ARM32 | Android ARM32 |
|---|---|---|---|---|---|---|
| C++ 오브젝트/맹글링 | Itanium (동일) | Itanium (동일) | Itanium (동일) | Itanium (동일) | Itanium (동일) | Itanium (동일) |
| 호출 규약 | SysV | SysV | AAPCS64 | AAPCS64 | AAPCS | AAPCS |
| 예외 언와인드 | DWARF | DWARF | DWARF | DWARF | ARM EHABI | ARM EHABI |
| C 런타임 | glibc | bionic 아님 | glibc | bionic 아님 | glibc | bionic 아님 |
| C++ 표준 라이브러리 | libstdc++/libc++ | libc++(NDK) | libstdc++/libc++ | libc++(NDK) | libstdc++/libc++ | libc++(NDK) |
표준 구현체 이런거 어떤 기준으로 나뉘는지,
표준에서는 어느정도 범위까지 정해주고, ABI는 어떤 거에서 달라지고 또 운영체제에 따라 달라지는 부분은 뭔지 이런거
- 표준 vs ABI
ISO C++ 표준: 언어 의미론(가상 호출이 무엇을 해야 하는가, 예외가 어떻게 전파되어야 하는가 등)을 규정.
ABI(Application Binary Interface): 그 의미를 메모리 레이아웃·심볼·호출 규약·RTTI·예외 메타데이터·언와인더로 구체화.
vtable/RTTI 객체 배치, name mangling, 예외 테이블, 언와인더 루틴 등이 여기 포함.
- 대표 ABI 두 계열
Itanium C++ ABI: 리눅스/유닉스 계열에서 사실상 표준.
vtable[-2]=offset-to-top, vtable[-1]=type_info*.
예외: DWARF CFI + Itanium EH ABI(LSDA, personality __gxx_personality_v0 등).
MSVC C++ ABI(Windows):
vftable[-1]=Complete Object Locator → TypeDescriptor/CHD/BCD(PMD).
예외: MSVC EH(__CxxFrameHandler3 등) + OS의 SEH 기반.
- 컴파일러와 타깃의 관계
어떤 컴파일러냐보다 “무엇을 타깃하느냐”가 ABI를 결정합니다.
GCC/Clang이든:
-linux- 타깃 → Itanium C++ ABI
*-windows-msvc 타깃(Clang-cl, MSVC) → MSVC C++ ABI
*-windows-gnu(MinGW) → Itanium C++ ABI(단, x64 MinGW-w64는 언와인딩에 SEH 사용)
Visual Studio로 “Linux 원격 빌드”를 하면, 실제로는 GCC/Clang이 Itanium ABI로 빌드합니다.
- RTTI·예외는 ABI에 따라 실제 구조가 다름
RTTI
Itanium: __class_type_info / __si_class_type_info / __vmi_class_type_info 내부에 상속 그래프.
MSVC: COL→TypeDescriptor/CHD/BCD 연결 고리.
예외(throw/catch/unwind)
Itanium: DWARF 기반 언와인딩 + LSDA(landing pad) + personality 함수.
MSVC: OS의 SEH 프레임 데이터와 MSVC EH 메타(FuncInfo/UnwindMap/TryMap 등) + __CxxFrameHandler3.
호환성: 서로 다른 EH ABI 사이에서 예외를 모듈 경계로 넘기면 깨짐(같은 ABI/런타임 필요).
- SEH / VEH는 무엇이고 C++ 예외와 관계는?
SEH(Structured Exception Handling): Windows 커널/런타임의 OS 레벨 예외 체계(접근위반, 0으로 나눔 등). MSVC C++ 예외는 SEH를 바닥으로 활용해 프레임을 해석/언와인딩.
VEH(Vectored Exception Handling): SEH 전에 글로벌 체인으로 예외를 “가로채는” 메커니즘(디버거/보호 솔루션이 많이 씀).
C++ 예외는 언어 차원의 논리적 예외.
Windows/MSVC: C++ EH ⇄ SEH 연동(하지만 C++ 예외 obj와 타입 매칭은 MSVC EH 메타가 처리).
리눅스/Itanium: OS-독립적으로 DWARF 언와인딩과 personality가 처리.
- 추가로 알아두면 좋은 호환 이슈
name mangling(심볼 맹글링)도 ABI 일부 → 바이너리/링킹 호환성에 영향.
RTTI/예외 전파는 같은 ABI+런타임이어야 안전(특히 DLL/so 경계).
MinGW-w64(x64): C++ ABI는 Itanium이지만 언와인더는 SEH를 사용 → 섞을 때 주의.
- 한 줄 요약
표준은 동작을, ABI는 배선을 정한다.
타깃이 리눅스면 Itanium, Windows(msvc 타깃)면 MSVC ABI.
RTTI/예외/언와인딩 구조는 ABI에 따라 달라지고, SEH/VEH는 Windows의 OS 예외 메커니즘으로 MSVC EH의 기반이 된다.
각각의 계층에서 연구한 내용에 대해 링크를 연결
운영체제별 차이점 설명 #
운영체제와 C++을 사용한 응용프로그램 사이는 이렇게 많은 단계를 거친다.
1[하드웨어] ← arm, x86 등 아키텍쳐에 따른 ISA, 호출규약, 엔디안 등
2 ↓
3[커널/운영체제] ← 시스템콜 실제 구현체(sys_write, sys_...), 로더, 보안정책 등
4 ↓
5[libc/CRT] ← glibc/musl/bionic, MSVCRT/UCRT
6 ↓
7[C++ 런타임/STL] ← new/delete, 예외/RTTI, STL ...
8 ↓
9[사용자 프로그램]
플랫폼 ABI #
하드웨어 #
arm이나 x86에 따라서 ISA가 다르며, 레지스터 종류도 다르기 때문에 함수호출 규약이 달라진다.
커널/운영체제 #
libc/CRT #
C++ 런타임/STL #
계층마다 관련된 C++ 연구 링크 #
하드웨어 #
호출규약