← 문제로

57. 객체 정렬(alignment): alignof/alignas·패딩·over-aligned·new 정렬 보장

난이도 상
내 리뷰 · C++
해설 · C++

해설 — 객체 정렬(alignment): alignof/alignas·패딩·over-aligned·new 정렬 보장

난이도: 상

요약

정렬 요구는 각 타입이 놓일 수 있는 주소를 제한한다. 구조체는 멤버 정렬을 만족시키려 패딩을 넣고, 전체 크기는 가장 큰 멤버 정렬의 배수로 반올림된다. alignas 로 over-align 하면 크기·정렬이 커지고, C++17 부터 new 는 over-aligned 타입의 정렬을 보장한다.

예측 출력 (x86-64 Linux, g++)

1 4 8
12 4
16 16
16 8
4 8
aligned16

단계별 내부 동작

  1. alignof(char)=1, alignof(int)=4, alignof(double)=8. 각 타입은 자기 크기(스칼라)만큼 정렬되는 것이 일반적이다.
  2. struct A { char c; int i; char d; } → 레이아웃:
    • c @0 (1B)
    • 패딩 @1..3 (int 정렬 4 맞추기)
    • i @4..7
    • d @8 (1B)
    • 꼬리 패딩 @9..11 (전체 크기를 alignof(A)=4 의 배수로)
    • sizeof(A)=12, alignof(A)=4. → 출력 12 4.
  3. struct alignas(16) Over { int x; } → 정렬 16 강제. 크기도 정렬 배수라야 하므로 sizeof=16, alignof=16. → 16 16.
  4. struct Packed { char c; double d; } → c@0, 패딩@1..7, d@8..15. sizeof=16, alignof=8(가장 큰 멤버 double). → 16 8.
  5. offsetof(A,i)=4, offsetof(A,d)=8. 위 레이아웃과 일치.
  6. new Over() → C++17 부터 over-aligned 타입은 정렬-인지 operator new(operator new(size_t, align_val_t))로 할당되어 16바이트 정렬이 보장된다. 주소 % 16 == 0 → aligned16.

흔한 오해·함정

  • 멤버 순서로 크기가 달라진다: A 를 { int i; char c; char d; } 로 재배열하면 i@0..3, c@4, d@5, 꼬리 패딩@6..7 → sizeof=8. 큰 멤버부터 배치하면 패딩이 준다(구조체 팩킹 최적화).
  • sizeof 는 배열 스트라이드: 꼬리 패딩이 있는 이유는 배열 원소 간 정렬을 유지하기 위해서다. A a[2] 에서 a[1] 도 4바이트 정렬돼야 하므로 sizeof(A) 가 정렬 배수여야 한다.
  • C++11/14 의 new 는 over-align 미보장: C++17 이전에는 new Over() 가 16바이트 정렬을 보장하지 않아 alignas(64) SPSC 큐 노드 등이 잘못 정렬될 수 있었다. C++17 정렬-인지 new, 또는 std::aligned_alloc/posix_memalign 이 필요했다.
  • std::atomic 과 정렬: 원자 연산의 lock-free 보장과 성능은 대상이 자연 정렬돼 있을 때 성립한다. 잘못 정렬된(packed) 원자 변수는 성능 저하 또는 플랫폼에 따라 오작동한다.

면접 포인트

  • 정렬이 중요한 3대 이유: (1) 하드웨어가 정렬 안 된 접근을 느리게/불가하게 처리, (2) false sharing 회피를 위해 캐시라인(보통 64B)으로 패딩·정렬, (3) 바이너리 직렬화에서 구조체를 그대로 memcpy 하면 패딩·정렬·엔디안 가정이 이식성을 깨뜨림.
  • 구조체 크기 계산: 멤버를 순서대로 각 멤버 정렬로 올림 배치 → 마지막에 전체를 최대 정렬로 올림. 멤버 재정렬로 패딩을 줄이는 것이 캐시·메모리 최적화의 기본기.
  • over-aligned 타입(예: alignas(64))은 C++17 정렬-인지 new 로 힙에서도 정렬이 보장된다.