본문 바로가기

Software/C/C++

[C++] Memory Packing

메모리 패킹


1.요약

#pragma pack을 이용하여 메모리를 특정 align으로 packing할 수 있다.


2.본문

데브피아에 다음과 같은 팁이 올라왔다.

-----------------------------------------------------------------

구조체 / 공용체 도는 클래스를 바이트 스트림으로 변화시켜야한다면 메모리를 패킹하자.
- 사용하는 구조체 또는 공용체 / 클래스를 바이트 스트림으로 변화시켜야한다면, 메모리를 1 바이트 정열을 시켜야 정확한 바이트 단위 크기를 갔습니다. 주로 통신 프로그램을 만드는 경우에 해당하겠군요

예 )
   
-----------------------------------------------------------------

그럼 왜 이런 방법을 사용해야하는지와 좀더 구체적인 용법을 알아보겠다.

먼저 alignment는 compiler에서 구조체/공용체 데이터를 처리하는 단위가 된다. 이것은 VC의 [Project Settings]의 C/C++탭의 Code Generation항목에서 조절 가능하다. 또한 컴파일러 옵션의 /Zp가 이러한 역활을 한다.

디폴트값은 8이다. 이것은 우리가 구조체를 만들면 8byte단위로 alignment가 정해진다는 의미이다.

다음과 같은 구조체가 있다.

typedef struct GTcpPacket 

{ 

    char mark1[8]; 

    int len; 

}GTCPPACKET, FAR * LPGTCPPACKET, *PGTCPPACKET; 

위 구조체의 크기를 계산해보면

GTcpPacket packet; 

int nAllSize = sizeof(packet); 

int nSize1 = sizeof(packet.mark1); 

TRACE("size:%d, %d\n", nAllSize, nSize1); 

size:12, 8

구조체 전체의 크기가 12(8+4)이고 mark1의 크기가 8임을 알 수 있다.

typedef struct GTcpPacket 

{ 

    char mark1[9]; 

    int len; 

}GTCPPACKET, FAR * LPGTCPPACKET, *PGTCPPACKET; 

와 같이 mark1의 크기를 1byte 늘렸다.

결과를 출력해보면

size:16, 9

와 같이 된다. 이론적으로는 9+4 = 13되어야하겠지만 compiler의 alignmemnt의 영향으로 이러한 현상이 일어나는 것이다.

데이피아에 언급된 방법으로 alignment를 조절해보자.

#pragma pack(push,enter_byte_packing) 

#pragma pack(1) 

typedef struct GTcpPacket 

{ 

    char mark1[9]; 

    int len; 

}GTCPPACKET, FAR * LPGTCPPACKET, *PGTCPPACKET; 

#pragma pack(push,enter_byte_packing) 

size:13, 9

이경우는 결과가 정상적으로 나왔다.

#pragma pack을 쓰지않고도 VC의 프로젝트 세팅에서 이러한 alignment를 조절할 수 있다고 앞서 설명했다. 그러나 이러한 방법은 프로젝트 전체에 걸처서 적용되기때문에 특정 구문/파일에만 적용할때는 데브피아에서 언급한 #pragma pack을 사용한다.

그런 #pragma pack의 용법을 좀더 구체적으로 알아보자.

프로토타입

#pragma pack( [ [ { push | pop}, ] [  identifier, ] ] [ n ] ) 
push, pop이 있는것이 특이할 것이다. 이것은 컴파이러 스텍을 이용함을 의미한다. 즉 뒤에 붙는 identifier 로 push한 이후의 컴파일러 옵션은 n으로 지정한 값을 적용받고 pop하면 지정된 옵션이 풀리게된다.

MSDN의 예제에 보면 이러한 방법으로 헤더파일 전체를 특정 alignment로 지정하는것을 볼 수 있는데 다음과 같다.

#pragma pack( push, before_include1 ) 

#include "include1.h" 

#pragma pack( pop, before_include1 ) 

여기서 identifier는 그냥 구분자이므로 push때와 pop때에 일치되는것만 쓰면 어떤것이든 상관없다. 단... 중복해서 사용하지는 말기를...


Working with Packing Structures

Problems can occur when a structure requires more bytes than the programmer intended, especially when space requirements are paramount.

Structure Packing and Alignment

Structure packing interacts with compiler alignment behavior as follows.

  • If the packsize is set equal to or greater than the default alignment, the packsize is ignored.
  • If the packsize is set smaller than the default alignment, the compiler aligns according to the packsize value.

Thus, if the packsize is set to four, data types having a size of four, eight, or 16 bytes are aligned on addresses that are multiples of four. However, there is no guarantee that data types eight bytes in size (64 bits) are aligned on addresses that are a multiple of eight. The packsize has no effect on data types outside of a structure.

In addition, packing affects the alignment of the entire packed structure. For example, in a structure declared under #pragma pack(1), the alignment of all members are forced to one, regardless of whether they would have been naturally aligned even without packing.

The following techniques set a packsize, in bytes:

  • The command-line option /Zp - Pack Structure Members sets the packsize to n, in which n can be 1, 2, 4, 8, or 16, and in which 8 is the default.
  • The compiler directive #pragma pack([n]) sets the packsize to n, in which n can be 1, 2, 4, 8, or 16. If n is not specified, #pragma pack resets the packsize to its value at the beginning of compilation: either the value specified by /Zp - Pack Structure Members, or the default, which is 8 on most platforms.

    The pragma applies only from the point at which it occurs in the source. For example, the /Zp1option sets the packsize to 1, which causes the compiler to use no padding within structures. To avoid this problem, turn off structure packing or use the __unaligned keyword when accessing unaligned members of such structures through pointers.

Guidelines for packing structures

The following list shows possible solutions to the structure issue.

  • Reordering structure members

    If space requirements are critical, reorder the members of the structure so the same-sized elements are next to each other and pack tightly. Usually, you start with the largest members and work your way down to the smallest ones.

    Note: Reordering a structure assumes that the user has full control of the data structure, but the user might not have the freedom to rearrange the members. For example, the data structure might represent the layout of fields in a file on disk.

    Consider the following code example:

    struct x_
    {
       char a;     // 1 byte
       int b;      // 4 bytes
       short c;    // 2 bytes
       char d;     // 1 byte
    } MyStruct;

    If you reorganize the members of this structure, as shown in the following code example, the reorganized structure aligns all members on natural boundaries, and the size of the structure is eight bytes instead of 12.

    struct x_
    {
       int b;     // 4 bytes
       short c;   // 2 bytes
       char d;    // 1 byte
       char a;    // 1 byte
    } MyStruct;
  • Padding the structure

    A different kind of problem can arise when the structure size requires padding to make sure array elements have the same alignment, but the user needs to ensure that there is no padding between the array elements. For example, the user might need to restrict memory usage or read data from a fixed-format source.

    If the structure requires padding, you can use the compiler directive #pragma pack. However, #pragma pack causes elements of the structures to be unaligned, and it requires the use of the __unaligned qualifier to generate the code needed to access this data without causing alignment faults.

    The following example code uses #pragma pack to tell the compiler that the pointer px points to data that is not naturally aligned, and tells the compiler to generate the appropriate sequence of load, merge, and store operations to do the assignment efficiently.

    # pragma pack (1)
    struct x_
    {
       char a;    // 1 byte
       int b;     // 4 bytes
       short c;   // 2 bytes
    } MyStruct;
    # pragma pack ()
     
    void bar()
    {
      struct x_ __unaligned *px = &MyStruct;
       . . . .
       px->b = 5;
    }

    The __unaligned qualifier should only be used as a last resort, because the generated code is less efficient than accessing naturally aligned data. However, __unaligned is clearly preferable to alignment faults.

    If at all possible, arrange the members of a data structure to preserve alignment and minimize space at the same time.