서론 :

지금 까지 나는 byte padding이 cpu 레지스터 사이즈를 따라 일괄적으로 적용 되는 줄 알고 있었다(http://nsjokt.springnote.com/pages/4716509). 하지만 오늘 exe와 dll 사이에서 일어난 문제를 해결하며 찾아본 자료에서 byte padding은 아래의 네 가지 규칙에 따라 구조체에 따라 다르게 적용 된다는 사실을 알았다. 예를 들어 설명 하자면 지금까지 페이지 사이즈가 4byte인 어플리케이션에서는 byte padding 사이즈를 따로 지정해 주지 않는 한 1 byte 짜리 멤버 변수를 가진 구조체나 4 byte 멤버 변수를 가진 구조체의 sizeof 결과가 모두 4 byte로 같을 것이라고 생각 했으나 실제로는 각각 1 byte, 4 byte로 다르게 align 되고 있었다.

 

위에서 언급한 네 가지 규칙을 살펴 보자면(일단 VC 기준, gcc는 다른 옵션이 있겠지) :

  1. Unless overridden with __declspec(align(#)), the alignment of a scalar structure member is the minimum of its size and the current packing.
  2. Unless overridden with __declspec(align(#)), the alignment of a structure is the maximum of the individual alignments of its member(s).
  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.
  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.

출처 : http://msdn.microsoft.com/ko-kr/library/83ythb65(v=vs.100).aspx

 

나름 쉽게 풀어쓴다고 썼지만 나도 알아 먹기 힘드니 예제를 통해서 알아 보도록 하자

 

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;

 

output :

Size_1_Align:1
Size_4_Align:4

bool 는 1 byte, long은 4 btye, 각 구조체의 사이즈는 각각의 멤버 변수 사이즈 대로 만들어진다.

 

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;
}

output :
Size_1_Align:4
Size_4_Align:8

예상대로 각각 4 bypte 와 8 byte 가 나왔다.

 

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;
}

 

output :
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;
}

 

output :
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가 일괄 적용 된것이 아니었나 싶어 버젼을 명시한다).

Posted by kukuta

댓글을 달아 주세요

  1. Favicon of http://blog.ggamsso.wo.tc BlogIcon 깜쏘 2012.11.13 10:23  댓글주소  수정/삭제  댓글쓰기

    이게 플랫폼이 제한적이라면 상관이 없는데
    HP-UX, AIX, Solaris에서 다 다르게 SigBus 뜨면 미치죠.

  2. Favicon of http://kukuta.tistory.com BlogIcon kukuta 2012.11.14 23:34  댓글주소  수정/삭제  댓글쓰기

    뭔소리야? 패딩에 왠 sigbus? 구조체를 char 포인터로 강제 캐스팅해서 쓰고 있냐?
    아니면 HP, AIX, Solaris에서 바이트 패딩 룰이 다 다르다는 이야기야? 그래서 네트웍으로 전송할 때 값이 깨진단 의미??

  3. Favicon of https://jangjy.tistory.com BlogIcon 매직블럭 2014.07.23 17:07 신고  댓글주소  수정/삭제  댓글쓰기

    패딩 관련 문제로 매우 궁금하던차에 적절한 글 잘 봤습니다.

    감사합니다