C++ 클래스·링크·메모리 문제를 증상으로 찾는 표: Rule of Five, EBCO, 다이아몬드, 이름 맹글링, use-after-free
이 글의 핵심
클래스 설계와 링크, 메모리에서 생기는 문제를 증상 순서로 정리했습니다. 복사·이동 호출 순서, 빈 클래스의 크기, 다이아몬드 상속의 기본 클래스 개수, 맹글링된 심볼을 직접 실행해 확인했고, 각 증상의 자세한 글로 이어집니다.
객체 하나가 이중 해제로 죽거나, 구조체 크기가 이상하거나, 링크가 “undefined reference”로 실패할 때는 증상부터 찾아가는 편이 빠르다. 이 글은 증상별로 원인을 짧게 보여 주고 자세한 글로 연결한다. 아래 출력은 모두 Apple clang 21.0.0, macOS arm64, -std=c++20에서 직접 실행한 결과다.
증상 표
| 증상 | 실행으로 확인한 것 | 자세한 글 |
|---|---|---|
| 복사한 객체를 해제하다 죽음(이중 해제) | 복사 생성자를 직접 쓴 Buf는 ctor copy move copy assign 순서로 호출되어 의도한 경로가 선택됨 | Rule of Five 직접 구현 |
| 빈 멤버 때문에 구조체가 커짐 | struct{int x; Empty e;}는 8바이트, [[no_unique_address]]를 붙이면 4바이트 | EBCO와 no_unique_address |
| 기본 클래스 생성자가 두 번 호출됨 | 일반 다이아몬드는 Base()가 두 번, virtual 상속은 한 번 | 다이아몬드 상속 문제 |
링크 에러에 _Z... 심볼 | __ZN2ns1fEi를 c++filt로 풀면 ns::f(int) | 이름 맹글링 |
| 해제한 메모리를 읽는데 크래시가 안 남 | 일반 빌드는 쓰레기 값과 종료 코드 0, ASan은 heap-use-after-free | use-after-free |
증상별 설명
복사와 이동이 의도대로 호출되는지
Buf는 생성자, 복사 생성자, 이동 생성자, 값 전달 대입 연산자를 가진다. Buf a(8); Buf b = a; Buf c = std::move(a); b = c;를 실행하자 출력은 ctor copy move copy assign이었다. 복사 한 번, 이동 한 번, 그리고 대입은 매개변수 복사(copy) 뒤 교환(assign)으로 이루어졌다. 이것이 copy-and-swap 방식의 전형적인 호출 순서다. 연결 리스트나 해시 테이블처럼 자원을 직접 관리하는 자료구조를 구현하면 이 규칙이 바로 필요해지는데, 그런 구현 예는 C++로 자료구조 직접 구현하기에 있다.
객체 크기와 빈 클래스
빈 클래스도 객체는 서로 다른 주소를 가져야 해서 멤버로 두면 1바이트가 잡히고 정렬 때문에 8바이트가 된다. [[no_unique_address]]를 붙이자 4바이트로 줄었다. 크기가 중요한 라이브러리 코드에서 정책 객체를 멤버로 둘 때 의미가 있다.
다이아몬드 상속
L과 R이 Base를 각각 상속하고 Dia가 둘을 상속하면 Base()가 두 번 호출되고 sizeof(Dia)는 8이었다. virtual 상속에서는 Base()가 한 번만 호출됐지만 sizeof(VDia)가 24로 커졌다. 중복을 없애는 대가로 객체가 커지고 접근이 한 단계 간접이 된다.
링크 에러와 심볼
같은 이름의 f를 int와 double로 오버로드하고, extern "C" 함수도 하나 두어 심볼을 봤다. 맥에서 nm으로 본 심볼은 __Z1gv, __ZN2ns1C1mEi, __ZN2ns1fEd, __ZN2ns1fEi, _c_func였다. c++filt로 풀면 g(), ns::C::m(int), ns::f(double), ns::f(int)이 되고, extern "C" 함수는 맹글링되지 않고 _c_func 그대로였다. 리눅스에서는 맨 앞 밑줄이 하나 적다. C 라이브러리를 C++에서 쓸 때 extern "C"가 빠지면 이 차이 때문에 링크가 실패한다.
해제 후 사용
해제한 메모리를 읽어도 일반 빌드는 조용히 틀린 값을 낼 수 있다. ASan을 켜면 접근 위치와 해제 위치를 보여 주는 보고서가 나온다. 세 가지 패턴(delete 후 읽기, string_view, vector 재할당)의 비교는 use-after-free 글에 있다.
확인하지 못한 것
- GCC와 MSVC에서의 객체 크기와 심볼 모양.
- 가상 상속에서 생성자 인자 전달 규칙과 초기화 순서의 모든 경우.