들어 가며..
지금 까지 나는 byte padding이 cpu 레지스터 사이즈를 따라 일괄적으로 적용 되는 줄 알고 있었다. 하지만 오늘 exe와 dll 사이에서 일어난 문제를 해결하며 찾아본 자료에서 byte padding은 아래의 네 가지 규칙에 따라 구조체에 따라 다르게 적용 된다는 사실을 알았다. 예를 들어 설명 하자면 지금까지 페이지 사이즈가 4byte인 어플리케이션에서는 byte padding 사이즈를 따로 지정해 주지 않는 한 1 byte 짜리 멤버 변수를 가진 구조체나 4 byte 멤버 변수를 가진 구조체의 sizeof 결과가 모두 4 byte로 같을 것이라고 생각 했으나 실제로는 각각 1 byte, 4 byte로 다르게 align 되고 있었다.
바이트 패딩 규칙
msdn의 align (C++) 문서를 살펴 보면 아래와 같이 네 가지 바이트 패딩 규칙이 있다. (일단 VC 기준, gcc는 다른 옵션 있을지도 모른다) :
- Unless overridden with __declspec(align(#)), the alignment of a scalar structure member is the minimum of its size and the current packing.
- Unless overridden with __declspec(align(#)), the alignment of a structure is the maximum of the individual alignments of its member(s).
- A structure member is placed at an offset from the beginning of its parent structure which is the smallest multiple of its alignment greater than or equal to the offset of the end of the previous member.
- The size of a structure is the smallest multiple of its alignment larger greater than or equal the offset of the end of its last member.
나름 쉽게 풀어쓴다고 썼지만 나도 알아 먹기 힘드니 예제를 통해서 알아 보도록 하자
1. Unless overridden with __declspec(align(#)), the alignment of a scalar structure member is the minimum of its size and the current packing.
"__declspec(align(#))을 오버라이드 하지 않으면, 스칼라 구조체(long, bool과 같은 일반 변수로만 이루어진 구조체)의 멤버는 변수의 사이즈와 현재 지정된 byte padding align을 따른다."
이해를 돕기 위해 아래와 같이 두 가지 타입의 구조체를 만들어 보았다.
struct Size_1_Align
{
bool b;
};
struct Size_4_Align
{
long l;
};
int main()
{
std::cout << "Size_1_Align:" << sizeof(Size_1_Align) << std::endl;
std::cout << "Size_4_Align:" << sizeof(Size_4_Align) << std::endl;
}
결과
Size_1_Align:1
Size_4_Align:4
bool과 long으로만 이루어진 각 구조체의 사이즈는 1 byte, 4 byte와 같이 각각의 멤버 사이즈대로 align 되었다.
2. Unless overridden with __declspec(align(#)), the alignment of a structure is the maximum of the individual alignments of its member(s).
"__declspec(align(#))을 오버라이드 하지 않으면, 구조체는 멤버 변수중 가장 사이즈가 큰 멤버 변수의 byte padding align을 따른다."
그럼 위의 Size_1_Align 구조체에는 2 byte 변수를, Size_4_Align 구조체에는 long(4byte) 보다 작은 사이즈의 멤버 변수를 추가해 보겠다. 가장 큰 사이즈 멤버 변수의 align을 따른다고 했으므로 Size_1_Align은 2 byte align 규칙을 따를 것이고, Size_4_Align 구조체는 4 byte align 규칙을 따를 것이다 :
struct Size_1_Align
{
bool b; // byte padding 규칙에 따라 요 부분은 1 byte가 더 채워 질 것입니다.
short s;
};
struct Size_4_Align
{
long l;
bool b;
short s;
};
int main()
{
std::cout << "Size_1_Align:" << sizeof(Size_1_Align) << std::endl;
std::cout << "Size_4_Align:" << sizeof(Size_4_Align) << std::endl;
}
결과
Size_1_Align:4
Size_4_Align:8
Size_1_Align의 경우는 가장 사이즈가 큰 멤버인 short을 따라 2 byte로 align 되므로 1byte인 bool은 1byte가 더 채워져 short과 함께 4byte가 됩니다.
Size_4_Align의 경우는 가장 사이즈가 큰 멤버인 long을 따라 4 byte로 align 되므로 4 byte인 long으로 채워지고, 다음 bool 에 의한 1 byte, 마지막 short의 경우 2byte가 추가 되는데, 4 byte align이므로 3byte가 아직 남아 마저 채워진다. 그래서 전체 사이즈는 8byte가 된다.
3. A structure member is placed at an offset from the beginning of its parent structure which is the smallest multiple of its alignment greater than or equal to the offset of the end of the previous member.
"구조체 멤버는 부모 구조체의 byte padding align의 시작 offset에서 부터 이전 멤버 변수의 끝 offset 뒤에 자리 잡는다."
여기서 부모 구조체의 byte padding align에 대해서 잠깐 설명하자면, 부모가 4 byte padding 규칙을 가지고 실제 7 바이트 멤버만 가지고 있는 경우 7 byte 보다 큰 4 의 배수 byte offset을 시작 점으로 잡는다는 의미다. 예를 들어 :
struct Size_4_Align
{
long l;
short s; // bool b 와 위치가 바뀌었음.
bool b; // short s 와 위치가 바뀌었음.
};
struct Derived_4_Align : public Size_4_Align
{
char c;
};
int main()
{
std::cout << "Size_4_Align:" << sizeof(Size_4_Align) << std::endl;
std::cout << "Derived_4_Align:" << sizeof(Derived_4_Align) << std::endl;
}
결과
Size_4_Align:8
Derived_4_Align:12
Derived_4_Align 구조체는 Size_4_Align을 상속 받아 1 byte(char) 짜리 변수를 추가 했다. 생각해 보면 Size_4_Align의 경우 멤버 변수 나열이 4, 2, 1 byte로 나열 되었으므로 뒤에 1 바이트가 추가 되면 8 byte로 align이 될 수도 있지 않을까 생각했지만 에누리 없이 기존 8 byte에 더하여 (그리고 최대 사이즈 멤버 변수가 4 byte이므로) 12 byte 짜리 구조체가 되었다.
4. The size of a structure is the smallest multiple of its alignment larger greater than or equal the offset of the end of its last member.
"구조체의 사이즈는 가장 마지막 멤버 변수의 offset 을 기준으로 align 되는 byte의 최소 배수이며 offset과 같거나 큰 사이즈를 가진다."
말이 조금 달라지긴 했지만 위 3번에서 설명 했던 byte padding align과 같은 의미다. 결국 구조체의 사이즈는 byte padding을 통해 가장 페이징 하기 좋은 사이즈로 결정 된다는 의미다.
struct Size_8_Align
{
long long ll;
bool b;
};
int main()
{
std::cout << "Size_8_Align:" << sizeof(Size_8_Align) << std::endl;
}
결과
Size_8_Align:16
결국 가장 큰 멤버 변수 사이즈(8 byte)로 align 되는 구조체는 1 byte가 더 추가 되더라도 8 byte 가 증가 하며 (align 사이즈의 배수) 실제 구조체 사이즈인 9 byte 보다는 크다는 것이다.
요약
오늘의 핵심은 구조체의 byte padding은 모든 구조체에 일괄적용 되는 것이 아니라 규칙에 따라 각각의 최적의 align 사이즈를 가지고 있다는 것이다.
위 코드들은 vc++ 2010과 gcc 4.4.6 버젼 위에서 테스트 해보았다(혹시나 예전 버젼 컴파일렁에서는 padding byte가 일괄 적용 된것이 아니었나 싶어 버젼을 명시한다).