본문 바로가기

C++

C++의 구조체 멤버 맞춤(Structure member alignment), 클래스

반응형

C++에서 구조체의 멤버 맞춤(alignment)은 메모리 효율성과 접근 속도를 최적화하기 위해 구조체 내 멤버들이 메모리에 배치되는 방식을 말합니다.

이는 CPU가 메모리를 효율적으로 접근하기 위해 특정 크기 단위로 데이터를 배치하는 것을 요구하기 때문입니다.

 

기본 원칙은 구조체의 멤버 중 가장 큰 자료형의 정렬 크기(alignment requirement)에 맞춰 배치된다는 것인데, 이를 통해 CPU가 메모리 접근 시 잘못된 정렬(unaligned access)로 인해 발생하는 성능 저하를 방지합니다.

 

기본 규칙

멤버 변수는 자신의 크기 또는 구조체의 정렬 크기 중 작은 값에 맞춰 배치됩니다.

구조체의 크기는 전체 구조체의 정렬 크기의 배수가 됩니다.

 

패딩(padding)

멤버 변수 사이에 삽입되는 패딩은 멤버의 주소가 정렬 요구 사항을 충족하도록 하기 위해 삽입됩니다.

#include <iostream>
#include <cstddef>

struct Example {
    char a;      // 1바이트
    int b;       // 4바이트
    char c;      // 1바이트
};

int main() {
    std::cout << "Size of Example: " << sizeof(Example) << std::endl;
    std::cout << "Offset of a: " << offsetof(Example, a) << std::endl;
    std::cout << "Offset of b: " << offsetof(Example, b) << std::endl;
    std::cout << "Offset of c: " << offsetof(Example, c) << std::endl;
    return 0;
}
Size of Example: 12
Offset of a: 0
Offset of b: 4
Offset of c: 8

1. char a는 1바이트이고, 정렬 요구 사항은 1바이트입니다.

2. int b는 4바이트이고, 정렬 요구 사항은 4바이트이므로 b의 주소는 4바이트 경계에서 시작해야 합니다. 따라서 ab 사이에 3바이트의 패딩이 삽입됩니다.

3. char c는 1바이트이고, 정렬 요구 사항은 1바이트입니다. 하지만 구조체의 정렬 크기는 4바이트 단위이므로 끝에 3바이트의 패딩이 추가되어 총 크기가 12바이트가 됩니다.

 

 

패딩 최소화 방법

구조체의 크기를 줄이기 위해 패딩을 최소화하려면 멤버 변수를 정렬 크기가 큰 순서대로 선언하는 것이 좋습니다.

구조체의 전체 크기는 가장 큰 멤버의 정렬 요구사항의 배수가 되기 때문입니다.

 

먼저, 패딩 최소화를 고려하지 않은 구조체 선언입니다.

struct NonOptimized {
    char a;    // 1바이트
    int b;     // 4바이트
    char c;    // 1바이트
};
a: 0x00 (1바이트)
[패딩]: 0x01, 0x02, 0x03 (3바이트)
b: 0x04 (4바이트)
c: 0x08 (1바이트)
[패딩]: 0x09, 0x0A, 0x0B (3바이트)
=> 총 크기 12바이트 중 패딩 6바이트

 

패딩 최소화를 고려한 구조체 선언입니다.

struct Optimized {
    int b;     // 4바이트
    char a;    // 1바이트
    char c;    // 1바이트
};
b: 0x00 (4바이트)
a: 0x04 (1바이트)
c: 0x05 (1바이트)
[패딩]: 0x06, 0x07 (2바이트)
=> 총 크기 8바이트 중 패딩 2바이트

 


위의 구조체에 적용되는 메모리 정렬 및 패딩 관련 개념은 클래스의 경우에도 적용됩니다.

다만, 클래스는 구조체와 달리 상속이 가능하고, 가상  함수가 포함될 수 있습니다.

 

먼저, 상속의 경우입니다.

class Base {
    char a;  // 1바이트
};

class Derived : public Base {
    int b;   // 4바이트
};

=>
a: 0x00 (1바이트, Base 클래스)
[패딩]: 0x01, 0x02, 0x03 (3바이트)
b: 0x04 (4바이트, Derived 클래스)

기본 클래스의 멤버 -> 파생 클래스의 멤버 순서대로 메모리에 연속적으로 배치되기 때문에 이 과정에서 패딩이 추가될 수 있습니다.

 

가상함수의 경우 가상함수테이블에 대한 포인터가 추가됩니다. 이 포인터는 일반적으로 4바이트 또는 8바이트의 크기를 가지며, 클래스의 크기에 영향을 미치게 됩니다.


결론

클래스에서도 구조체와 동일한 방식으로 패딩과 정렬이 적용되며, 패딩 최소화 전략도 똑같이 사용할 수 있습니다. 하지만 클래스의 추가적인 요소(가상 함수, 상속 등)는 구조체보다 정렬과 크기 계산을 복잡하게 만들 수 있습니다. 패딩 최소화는 클래스 설계에서 중요한 최적화 기술이지만, 코드의 가독성과 유지보수성도 고려해야 합니다.

반응형

'C++' 카테고리의 다른 글

[C++]Linux - 스레드 동기화와 뮤텍스(Mutex)  (1) 2024.12.27
널(null) 포인터 nullptr에 대해  (0) 2024.12.23
const, constexpr, consteval에 대해  (0) 2024.12.23