Media Log

MSDN에 따르면 구조체의 디폴트 패킹 값은 8이다. 간혹 32비트 운영체제에서는 4바이트이고 64비트 운영체제에서는 8바이트라고 주장하는 사람들도 있는데 디폴트 패킹 크기는 컴파일러가 결정하지 운영체제가 결정하는 것이 아니다. MSVC에서 디폴트 패킹을 8바이트로 정한 이유는(32비트 운영체제에서 조차) 기본 타입 중 가장 큰 타입이 8바이트이기 때문이다. 만약 이후에 16바이트 포인터나 INT128 같은 타입을 기본 타입으로써 사용하는 날이 온다면, 그 때는 디폴트 패킹 값도 16바이트로 변경될 것으로 예상한다.

여기에 패킹을 잘 이해하고 있는지 알아보기 위한 좋은 질문이 있다.

strcut X
{
  char c1;
  char c2;
  char c3;
  char c4;
  char c5;
  char c6;
  char c7;
};

struct Y
{
  char c;
  double d;
  int i;
};

디폴트 패킹 값인 8을 사용한다고 할 때 구조체 X와 Y의 크기는 각각 얼마일까?

잠시 생각해보고 아래를 클릭해서 답을 확인해보도록 하자.

더보기


구조체의 멤버들은 자신의 크기의 배수로 정렬되는 것이 좋다. char는 1의 배수, short은 2의 배수, int는 4의 배수, double은 8의 배수의 메모리 번지 주소에 위치하고 있을 때 우리는 해당 데이터가 정렬되어 있다고 말한다.
x86호환 아키텍쳐에서 윈도우즈 응용 프로그램을 만들 경우에는 정렬이 되어있지 않을 때 CPU가 메모리에 다시 접근하려고 시도하면서 성능이 떨어지게 된다. 다른 아키텍쳐에서는 응용이 크래시가 나거나 따로 예외 핸들링을 해주어야 할 수도 있다.

컴파일러는 데이터를 정렬시키기 위해서 구조체의 적당한 위치에 패딩을 집어넣는다. 조금 생각해보면 위 Y구조체에 마지막 4바이트 패딩은 필요가 없을 것 같다. 중간에 넣은 7바이트 패딩으로 인해 3개의 필드가 모두 잘 정렬이 된 것 같은데 말이다.
뒷 부분에 4바이트 패딩을 넣은 이유는 구조체가 배열에서 사용될 때 구조체의 멤버들이 메모리의 정렬된 위치에 올라가도록 하고 싶기 때문이다. 뒷 부분에 패딩을 넣지 않았으면 int나 double 타입이 자신의 타입에 맞게 정렬된 주소에 올라가지 못했을 것이다.

다음 Z구조체를 보자. 위의 Y구조체에서 double과 int의 위치만 바꾸었다. -위치만 바꾸었는데 패딩이 Y구조체와 다르게 들어가는 것에 대해서도 유심히 살펴 보아야 한다.
struct Z
{
    char c;
    // pad[3]
    int i;
    double d;
};

int _tmain(int argc, _TCHAR* argv[])
{
    // 다음 코드를 사용해서 어떻게 padding이 들어가 있는지 쉽게 확인해볼 수 있다.
    printf("position c:%d\n", FIELD_OFFSET(Z, c));
    printf("position i:%d\n", FIELD_OFFSET(Z, i));
    printf("position d:%d\n", FIELD_OFFSET(Z, d));
    printf("Total size:%d\n", sizeof(Z));
}
직접 코드를 실행시켜보는 것도 좋고, 아래 그림을 보고 이해해도 좋다. 이 구조체가 배열에서 사용될 때에는 아래와 같은 레이아웃을 갖게 될 것이다.

char는 1의 배수에, int는 4의 배수에, double은 8의 배수에 정렬되어져 올라가 있는 것을 주목하라. 진한 파란색으로 표시된 3바이트 패딩이 있기 때문에 가능한 일이다.

맨 처음 문제에서 X구조체의 크기가 8bytes가 아니라 7bytes인 이유는 모든 멤버가 char이기 때문이다. char는 1의 배수인 어느 주소에나 올라가도 되므로 padding을 집어 넣지 않아도 모든 멤버가 항상 자신이 원하는 주소에 올라가게 된다.
Y구조체가 Z구조체와 멤버 위치만 바꾸었는데 다른 레이아웃을 가지고 있는 이유도 그림을 그리면서 확인해보면 이해할 수 있을 것이다.

8로 패킹한다는 것은 구조체의 크기를 8의 배수로 맞추겠다는 것이 아니라, 크기가 8보다 큰 멤버가 있을 때는 정렬을 포기한다는 것을 뜻한다. 즉, 크기가 8보다 작은 타입에 대해서만 정렬하려고 시도하며, 이것은 다른 말로, 변수의 메모리 주소를 최대 8의 배수로 정렬한다는 뜻이 된다.
만약 패킹 크기를 4로 바꾼다면 double이나 int64_t 같은 타입들이 사용되었을 때 더 이상 정렬이 보장되지 않게 된다. 왜 디폴트 값을 8로 정했는지 이해가 되는가?

구조체를 만들 때는 어떻게 패킹이 될지 잘 예상해서 조각을 맞추듯이 만들어야지 아무 순서로나 마구 쑤셔넣는 것은 프로답지 못하다. 마이크로소프트에서 만든 거의 대부분의 구조체들은 이런 사소한 것들까지 잘 고려해서 만들어져 있다.
저작자 표시 비영리 동일 조건 변경 허락
신고
  1. 황후순 at 2011.12.19 09:02 신고 [edit/del]

    틀린 내용인거 같은데... double이 많을까요? Pointer가 많을까요?
    포인터가 32비트에서 4바이트고 64비트 8바이트라서 패킹을 각각 하고 있습니다. 구조체 크기랑은 별개의 이야기지요.메모리크기는 당연히 데이터 사이즈대로 나오겠죠. 패킹과 메모리사이즈는 별개의 얘기죠.
    단편화 생기는 과정을 만들어서 프로그램이 어떻게 죽는지 확인해보시기 바랍니다.
    필자는 ms vs compiler만 확인해보신 것이 아닌지... 메모리 패킹은 운영체제가 변경됨에 따라도 얼마든지 바뀔수 있으니 무조간 8바이트라고 단정하는건 문제가 됩니다.

    Reply
    • Favicon of http://www.benjaminlog.com BlogIcon 김재호 at 2011.12.19 10:10 신고 [edit/del]

      패킹이랑 메모리 사이즈는 별개의 이야기가 아닙니다. 그리고 패킹은 운영체제와는 상관없는 이야기이고요.
      무조건 8바이트라고 단정한 것이 아니라 왜 기본값을 8바이트로 정했는가에 대해서 말해본거에요.

      프로그램이 어떻게 죽는지 확인해보라는 말을 조금만 더 자세히 설명해 주실수 있을까요? 무슨 이야기를 하고 싶으신건지 궁금하네요^^

  2. kim at 2014.05.08 09:10 신고 [edit/del]

    돌아다니다 여기까지왔네용.
    .
    글 잘봤습니다.

    제가 내린 결론은 운영체제가 몇비트 머신인지.. 그리고 컴파일러가 뭐인지에 따라
    패킹바이트가 달라진다는 결론을 얻었습니당...

    맞나요 ?!

    Reply
    • Favicon of http://sunyzero.tistory.com BlogIcon sunyzero at 2014.05.12 16:29 신고 [edit/del]

      패딩은 XDR 표준과 관련이 깊습니다. 단지 컴파일러가 CPU를 효율적으로 쓰기 위해서만은 아닙니다.

      이는 CPU의 효율면에서만 정한 것이 아니라 잠재적으로 다른 머신과의 통신(심지어 호스트 내부 통신이라고 할지라도...)에 정렬 오류를 없애기 위해서 제정되었습니다.

      과거에는 희귀한 64비트 머신인 크레이을 제외하였고, 64bit는 32bit 정렬을 포함하기 때문에 RFC에서는 4Byte 정렬을 기준으로 내세우고 있습니다. 따라서 double형을 쓰지 않는다면 대개 표준에서 지정한 4Byte정렬을 사용하고 있습니다.

      RFC문서를 보시면 좀더 빠르게 이해하실 수 있습니다. 원래는 RFC1014였다고 1832로 리바이스 되었습니다. (discussion부분을 보시면 왜 4B를 표준에 사용했는지 나오고 있습니다.)
      http://tools.ietf.org/html/rfc1832

submit