Empty Base Optimization

2022. 9. 13. 13:32C++

C++은 모든 오브젝트가 최소 1byte의 크기를 가지고 있어야 하는데, 같은 타입의 오브젝트들의 주소 값이 각각 구별되어야 하기 때문이다. 이 제약은 클래스 멤버 변수로 선언된 객체와 같은 member suboejct라고 달라지지 않는데, 이 제약이 적용되지 않을 때가 있다. 바로 크기가 0인 클래스가 상속된 경우이다. 다음 코드를 보자.

 

struct Base
{
	// Nothing
}

struct Derived : Base
{
    int x;
}

 

어떤 자식 클래스의 객체를 생성하면 멤버 변수의 객체들 뿐만 아니라 부모 클래스 객체 또한 생성되는데, 이 부모 객체를 base class subobject라고 하며, 멤버 변수들을 member subobject라고 한다. 따라서 위 코드의 Derived 클래스 메모리 레이아웃은 다음과 같이 구성된다. (참고로 subobject가 아닌 객체들을 complete object라고 한다.)

모든 오브젝트가 최소 1byte의 크기를 가진다는 제약에 따르면 위 코드에서 Derived 클래스는 alignment padding을 고려하면 8byte를 가지는 것이 맞을 것이다. 하지만, sizeof(Derived)의 값은 4가 되는데, C++ 표준에 의해 크기가 0인 base class subobject는 0 byte를 차지하도록 보장되어 있기 때문이다.

An object has nonzero size if it
- is not a potentially-overlapping subobject, or
- is not of class type, or
- is of a class type with virtual member functions or virtual base classes, or
- has subobjects of nonzero size or unnamed bit-fields of nonzero length.
Otherwise, if the object is a base class subobject of a standard-layout class type with no non-static data members, it has zero size.

이러한 최적화를 Empty Base Optimization, 줄여서 EBO라고 한다.

 

EBO가 적용되지 않는 경우도 존재하는데, 자식 클래스의 base class subobject의 타입이 첫 번째 member subobject와 타입이 일치하거나 부모 타입인 경우가 그렇다. 

struct Base
{
};

struct Derived1 : Base
{
    int x;
};

struct Derived2 : Base
{
    Derived1 d;
};

이 경우 Derived2 객체의 메모리 레이아웃은 다음과 같을 것이다.

하지만 이 글 처음에 같은 타입의 객체들은 주소 값이 구별되야 한다고 했다. 즉, Derived2와 Derived1의 base class subobject의 크기가 둘 다 0이면 주소가 같게 되므로 EBO가 적용이 될 수 없는 것이다. 따라서 이 경우는 Derived2의 base class subobject의 크기가 1이 되며, sizeof(Derived2)의 값은 alignment padding을 고려하면 4가 아닌 1byte (Derived2::Base) + 3byte (padding) + 4byte (Derived1::x) = 8이 된다. (clang, gcc는 8을 출력하는데, msvc에서는 4를 출력한다. msvc가 표준을 안따르는건가..?)

 

[참고]

- http://eel.is/c++draft/intro.object

- http://eel.is/c++draft/class

- https://en.cppreference.com/w/cpp/language/ebo