ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 구조체 바이트 정렬은 만능이 아니다
    개발/C++ 2019. 10. 19. 17:13

    pragma once에 이은 만능 시리즈는 아니다. 이 기능들을 별 생각 없이 사용했다가 뇌 곳곳에서 유혈이 낭자한 끝에 얻은 교훈을 남기고 싶을 뿐이다. 공교롭게 둘 다 pragma 키워드를 사용하는 것은 우연의 일치일까. 심도 있게 설명하고 싶지만 아직은 능력이 부족한 탓에 수박 껍질만 핥아보려고 한다. 핥핥핥.

     

    제대로 된 실무 코드는 제대로 본 적이 없지만 소켓 프로그래밍에서는 패킷 헤더는 보통 구조체 바이트 정렬을 하는 것으로 보인다. 구조체 정렬은 함부로 쓰면 안 되지만 패킷 헤더에는 언제나 같은 크기의 데이터가 들어가(야 하)며, 동적으로 메모리를 할당할 일이 없기에 괜찮은 듯하다.

    1
    2
    3
    4
    5
    6
    7
    #pragma pack(push, 1)
        struct PacketHeader
        {
            short packet_size_;
            short id_;
        };
    #pragma pakc(pop)

     

    바이트 정렬을 하지 않은 구조체는 멤버 변수의 선언 순서에 따라 패딩 비트의 수가 달라진다. 크기가 가장 큰 멤버 변수의 바이트를 기준해서 메모리가 정렬된다. 한 가지 샘플 코드를 통해 한 번 짚고 넘어가보자. 그림을 곁들이면 이해를 빨리 돕는 글이 되겠지만 텍스트로만 해서 후다닥 끝내고 싶다아아. 정렬하지 않은 다음의 구조체는 크기가 몇일까?

    1
    2
    3
    4
    5
    6
    7
    struct Data1
    {
        char a;
        short b;
        int c;
        double d;
    };

    단순하게 생각하면 1 + 2 + 4 + 8해서  15바이트가 나올 것 같다. 메모리 정렬을 1로 했으면 그랬겠지만 실제로는 16바이트가 계산된다. 앞서 말했듯 크기가 가장 큰 자료형 기준으로 메모리가 잡히는데, double이 가장 크기 때문에 8바이트 단위로 구조체 멤버가 메모리에 할당되는데 a조차도 그렇다. 그렇다고 해서 구조체 크기가 8 X 4인 32는 아니다. 캐릭터 변수인 a에도 8바이트가 할당된 것은 맞는데 뒤에 남은 공간에 b와 c가 할당된다. 앞에서부터 순서대로 들어가는 것은 아니며 b가 끝을 기준으로 4바이트, short가 그다음 2바이트를 차지한다. 주소를 찍어보면 더 정확하게 알 수 있다.

    1
    2
    3
    4
    5
        Data1 d1;
        printf("%lld\n"sizeof Data1);    
        printf("%p\n"&d1.a);
        printf("%p\n"&d1.b);
        printf("%p\n"&d1.c);

     

    주소가 다음과 같다.

    끝자리를 보면 8부터 시작하므로 8바이트가 잡히면 F까지다.  int 변수가 CDEF를, short 변수가 AB를 차지하고 char 변수가 8만 차지해 남는 메모리는 9번지가 된다. 이렇게 해서 8바이트 안에 char, short, int변수가 다 들어갔다. 순서가 중요하다고 했는데, 다음과 같이 순서를 바꾼 구조체는 크기가 24가 나온다.

    1
    2
    3
    4
    5
    6
    7
    struct Data2
    {
        char a;
        double b;
        int c;
        short d;
    };

     

    이러한 작동원리 때문에 고민 없이 구조체 메모리 정렬을 하게 되면 메모리에 아예 접근하지 못하는 일이 생기기도 한다. 바로 겪었던 문제가 있는데, WSASend()를 호출하면 에러가 발생하는 것이었다. 에러가 발생할 일이 없는데 왜 에러가 발생하는지, 일단 화가 났지만, 십중팔구 사용자의 잘못이기 때문에, 화를 가라앉히며 WSAGetLastError()로 에러 코드를 확인해봤다. 10014. 듣도 보도 못한 숫자다. MSDN으로 확인해본 결과 유효하지 않은 포인터가 들어가면 발생하는 오류였다.

     

    WSASend()에서 포인터가 문제될 만한 매개변수는 WSABUF 변수의 buf밖에 없다. 하지만 buf에 대입한 메모리는 동적으로 할당이 되어 메모리가 중간에 해제될 일이 없는데 대체 무슨 일일까 생각하며 구조체 목록을 살펴봤다. WSABUF변수가 선언된 구조체가 구조체 바이트를 1로 정렬하는 영역 안에 있었다. 다른 곳으로 옮겨주니 WSASend()의 오류는 사라졌다.

    댓글

Designed by Tistory.