C++ 객체 메모리 레이아웃과 패딩
C++ 컴파일러는 타입의 정렬 요구사항에 맞춰 구조체 멤버 사이에 패딩(padding) 바이트를 삽입합니다. 멤버 선언 순서만 바꿔도 구조체 크기가 크게 달라질 수 있으며, 이는 메모리 사용량과 캐시 성능에 직접 영향을 줍니다.
1. 패딩 발생 원리
섹션 제목: “1. 패딩 발생 원리”struct Bad { char a; // 1바이트, offset 0 // 패딩 3바이트 (int 정렬 맞추기) int b; // 4바이트, offset 4 char c; // 1바이트, offset 8 // 패딩 3바이트 (구조체 끝 정렬)};// sizeof(Bad) == 12
struct Good { int b; // 4바이트, offset 0 char a; // 1바이트, offset 4 char c; // 1바이트, offset 5 // 패딩 2바이트};// sizeof(Good) == 8규칙: 크기가 큰 멤버를 먼저 선언하면 패딩을 최소화할 수 있습니다.
2. offsetof와 sizeof로 레이아웃 확인
섹션 제목: “2. offsetof와 sizeof로 레이아웃 확인”#include <cstddef>
struct Particle { float x, y, z; // offset 0, 4, 8 uint32_t color; // offset 12 float vx, vy, vz; // offset 16, 20, 24 float lifetime; // offset 28};// sizeof(Particle) == 32
static_assert(offsetof(Particle, color) == 12);static_assert(sizeof(Particle) == 32);3. alignas — 정렬 제어
섹션 제목: “3. alignas — 정렬 제어”// SIMD 연산을 위해 16바이트 정렬 강제struct alignas(16) SimdVector{ float x, y, z, w;};static_assert(alignof(SimdVector) == 16);
// 캐시 라인(64바이트) 정렬 — False sharing 방지struct alignas(64) ThreadLocalData{ std::atomic<int> counter; // 패딩으로 다른 스레드 데이터와 캐시 라인 분리};4. #pragma pack — 패딩 제거
섹션 제목: “4. #pragma pack — 패딩 제거”// 네트워크 패킷, 파일 포맷 등 바이트 단위 호환성이 필요할 때#pragma pack(push, 1)struct NetworkPacket{ uint8_t type; uint16_t length; uint32_t checksum; uint8_t data[256];};#pragma pack(pop)// sizeof(NetworkPacket) == 263 (패딩 없음)주의:
#pragma pack은 정렬되지 않은 접근으로 성능 저하나 하드웨어 예외를 유발할 수 있습니다.
5. Empty Base Optimization (EBO)
섹션 제목: “5. Empty Base Optimization (EBO)”struct Empty {}; // sizeof(Empty) == 1 (최소 1바이트)
struct WithMember { Empty e; // 1바이트 // 패딩 3바이트 int value; // 4바이트};// sizeof(WithMember) == 8
// EBO: 빈 기반 클래스는 크기 0으로 최적화됨struct WithBase : Empty { int value;};// sizeof(WithBase) == 4 (EBO 적용)STL 알로케이터, 정책 기반 클래스 등에서 광범위하게 활용됩니다.
6. 캐시 라인 최적화 — Hot/Cold 분리
섹션 제목: “6. 캐시 라인 최적화 — Hot/Cold 분리”// 나쁜 예: Hot 데이터와 Cold 데이터가 혼재struct Enemy { // Hot (매 프레임 접근) float x, y, z; float health; // Cold (거의 접근 안 함) std::string name; // 32바이트 std::string description; // 32바이트 int loot_table_id;};
// 좋은 예: Hot/Cold 분리struct EnemyHot { float x, y, z; float health;}; // 16바이트 — 캐시 라인 절반
struct EnemyCold { std::string name; std::string description; int loot_table_id;};
struct Enemy { EnemyHot hot; EnemyCold* cold; // 포인터로 분리};7. 구조체 크기 체크 — static_assert 활용
섹션 제목: “7. 구조체 크기 체크 — static_assert 활용”// 의도치 않은 크기 변경 방지struct Config { int32_t version; uint32_t flags; float values[8];};
static_assert(sizeof(Config) == 40, "Config 크기가 변경되었습니다. 직렬화 코드를 확인하세요.");8. std::is_standard_layout
섹션 제목: “8. std::is_standard_layout”#include <type_traits>
struct Pod { int x; float y; };struct NonPod { virtual void f() {} }; // vtable pointer 포함
static_assert(std::is_standard_layout_v<Pod>);static_assert(!std::is_standard_layout_v<NonPod>);
// Standard layout: C 구조체와 호환, memcpy 안전9. 가상함수와 vtable 오버헤드
섹션 제목: “9. 가상함수와 vtable 오버헤드”struct NoVirtual { int x; int y;};// sizeof(NoVirtual) == 8
struct WithVirtual { virtual void f() {} int x; int y;};// sizeof(WithVirtual) == 16 (64비트: vptr 8바이트 + int x 4 + int y 4)// vptr이 구조체 앞에 삽입되어 8바이트 오버헤드 발생
struct Derived : WithVirtual { void f() override {} int z;};// sizeof(Derived) == 20 (vptr 8 + x 4 + y 4 + z 4)// vptr은 공유됨 — 파생 클래스마다 새로 추가되지 않음
static_assert(sizeof(WithVirtual) == 16); // 64비트 플랫폼 기준10. 정렬 요구사항 조회
섹션 제목: “10. 정렬 요구사항 조회”#include <type_traits>#include <cstddef>
// alignof: 타입의 정렬 요구사항 (바이트)static_assert(alignof(char) == 1);static_assert(alignof(short) == 2);static_assert(alignof(int) == 4);static_assert(alignof(double) == 8);
// std::max_align_t: 기본 힙 할당의 최대 정렬값std::cout << alignof(std::max_align_t) << "\n"; // 보통 8 또는 16
// 동적 할당에서 정렬 지정 (C++17)void* p = ::operator new(64, std::align_val_t{64}); // 64바이트 정렬::operator delete(p, std::align_val_t{64});구조체 멤버를 크기 내림차순으로 선언하면 패딩을 최소화할 수 있습니다. 캐시 민감한 코드에서는 Hot 데이터를 한 캐시 라인(64바이트)에 집중시키고, SIMD 활용 구조체는 alignas(16/32)로 정렬하세요. static_assert(sizeof(T) == N)으로 직렬화 구조체의 레이아웃이 변경되지 않도록 보호하세요.
| 기법 | 목적 |
|---|---|
| 멤버 크기 내림차순 선언 | 패딩 최소화 |
alignas(N) | SIMD, 캐시 라인 정렬 강제 |
#pragma pack(1) | 네트워크/파일 구조체 패딩 제거 |
EBO (빈 기반 클래스 최적화) | allocator 등 빈 클래스 크기 0 처리 |
| Hot/Cold 분리 | 캐시 효율 극대화 |
static_assert(sizeof(T)==N) | 의도치 않은 레이아웃 변경 방지 |