Skip to content

44. 구조체 멤버 정렬


1. 키워드

  • 구조체 멤버 정렬
  • 패딩(Padding)


2. 구조체 멤버 정렬 사용하기

  • 구조체가 메모리에 올라갔을 때 멤버를 정렬하는 기능에 대해 알아보자.
  • 컴퓨터에서 CPU가 메모리에 접근할 때 32비트 CPU는 4바이트 단위, 64비트 CPU는 8바이트 단위로 접근한다.
  • 만약 32비트 CPU에서 4바이트보다 작은 데이터에 접근할 경우 내부적으로 시프트 연산이 발생해서 효율이 떨어진다.
  • 그래서 C 컴파일러는 CPU가 메모리의 데이터에 효율적으로 접근할 수 있도록 구조체를 일정한 크기로 정렬을 하게 된다.
  • 예를 들어 구조체 크기가 15나 17바이트가 되면 접근 효율이 떨어지게 되므로 2, 4, 8, 16바이트 단위로 정렬을 하는 것이다.


3. 구조체 크기 알아보기

  • 구조체 정렬을 하기 전에 먼저 구조체의 크기부터 알아보자.
  • 구조체의 전체 크기는 sizeof 연산자를 사용하면 알 수 있다.


sizeof(struct 구조체)
sizeof(구조체별칭)
sizeof(구조체변수)
sizeof 구조체변수


  • 이제 가상의 네트워크 패킷 구조체 PacketHeader를 정의해서 멤버의 크기와 구조체의 크기를 구해 보자.


#include <stdio.h>

struct PacketHeader
{
    char flags; // 1바이트
    int seq;    // 4바이트
};

int main()
{
    struct PacketHeader header;

    printf("%d\n", sizeof(header.flags));        // char 타입은 1바이트
    printf("%d\n", sizeof(header.seq));          // int 타입은 4바이트
    printf("%d\n", sizeof(header));              // 구조체 전체 크기는 8바이트
    printf("%d\n", sizeof(struct PacketHeader)); // 구조체 이름으로 크기 구하기

    return 0;
}

// 1
// 4
// 8
// 8


  • 구조체의 크기를 구하려면 sizeof(header)처럼 sizeof 연산자 안에 변수를 넣어주거나 sizeof(struct PacketHeader)처럼 struct 키워드와 구조체 이름을 넣어주면 된다.
  • PacketHeader 구조체 안에는 1바이트 크기의 char 타입과 4바이트 크기의 int 타입이 들어있다.
  • 그래서 전체 크기는 5바이트가 나와야 할 것 같은데 실제로는 8바이트가 나왔다.


  • C에서는 구조체를 정렬할 때 멤버 중에서 가장 큰 자료형 크기의 배수로 정렬한다.
  • 여기서 가장 큰 자료형은 int 타입이므로, int 타입의 크기 4바이트를 기준으로 정렬한다.


001


  • 4바이트로 정렬해서 flagsseq가 모두 들어가는 최소 크기는 8바이트이다.
  • 따라서 구조체 정렬을 하면 5바이트가 아닌 8바이트가 된다.
  • 여기서는 1바이트 크기의 char flags 뒤에는 4바이트를 맞추기 위해 남는 공간에 3바이트가 더 들어간다.
  • 이렇게 구조체를 정렬할 때 남는 공간을 패딩이라고 부른다.


  • 그럼 정말 구조체를 정렬한 뒤 멤버의 위치가 위의 그림처럼 되었는지 확인해 보자.
  • stddef.h 헤더 파일에 정의되어 있는 offsetof 매크로를 사용하면 구조체에서 멤버의 위치를 구할 수 있다.


offsetof(struct 구조체, 멤버)
offsetof(구조체별칭, 멤버)
#include <stdio.h>
#include <stddef.h> // offsetof 매크로가 정의된 헤더 파일

struct PacketHeader
{
    char flags; // 1바이트
    int seq;    // 4바이트
};

int main()
{
    printf("%d\n", offsetof(struct PacketHeader, flags));
    printf("%d\n", offsetof(struct PacketHeader, seq));

    return 0;
}

// 0
// 4


  • offsetof 매크로에 구조체와 멤버를 지정하면 구조체에서 해당 멤버의 상대 위치가 반환된다.
  • 첫 멤버의 상대 위치는 0이다.
  • 여기서는 구조체가 4바이트 단위로 정렬하므로 seq의 위치는 1이 아닌 4가 나온다.


4. 구조체 정렬 크기 조절하기

  • C에서는 구조체를 정렬하는 표준 방법이 없다.
  • 하지만 각 컴파일러에서 제공하는 특별한 지시자를 사용하면 구조체 정렬 크기를 조절할 수 있다.


1] Visual Studio, GCC 4.0 이상

#pragma pack(push, 정렬크기)
#pragma pack(pop)

2] GCC 4.0 미만

__attribute__((aligned(정렬크기), packed))


  • 다음 내용을 입력한 뒤 실행해 보자.


#include <stdio.h>

#pragma pack(push, 1) // 1바이트 크기로 정렬
struct PacketHeader
{
    char flags; // 1바이트
    int seq;    // 4바이트
};
#pragma pack(pop) // 정렬 설정을 이전 상태(기본값)로 되돌림

int main()
{
    struct PacketHeader header;

    printf("%d\n", sizeof(header.flags)); // char 타입은 1바이트
    printf("%d\n", sizeof(header.seq));   // int 타입은 4바이트
    printf("%d\n", sizeof(header));       // 1바이트 단위로 정렬했으므로
                                          // 구조체 전체 크기는 5바이트

    return 0;
}

// 1
// 4
// 5


  • 구조체를 정의할 때 위 아래로 #pragma pack(push, 1)#pragma pack(pop)을 넣어줬다.
  • 여기서 중요한 부분은 pack 안의 1이다.
  • C에서 자료형의 크기는 바이트 단위이고 가장 작은 크기는 1바이트이다.
  • 따라서 pack1로 설정하면 1바이트 단위로 정렬하게 되므로 남는 공간이 없이 자료형 크기를 그대로 메모리에 올라간다.


002


  • #pragma pack(push, 1)을 한 번 사용하면 그 아래에 오는 모든 구조체에 영향을 주므로 정렬 설정을 한 뒤에는 #pragma pack(pop)을 사용하여 설정을 이전 상태로 되돌린다.


  • 만약 GCC 버전이 4.0 미만이라면 #pragma pack(push, 1), #pragma pack(pop) 대신 __attribute__((aligned(1), packed))를 사용한다.


#include <stdio.h>

struct PacketHeader
{
    char flags;                        // 1바이트
    int seq;                           // 4바이트
} __attribute__((aligned(1), packed)); // GCC 4.0 미만. 1바이트 크기로 정렬

int main()
{
    struct PacketHeader header;

    printf("%d\n", sizeof(header.flags)); // char 타입은 1바이트
    printf("%d\n", sizeof(header.seq));   // int 타입은 4바이트
    printf("%d\n", sizeof(header));       // 1바이트 단위로 정렬했으므로
                                          // 구조체 전체 크기는 5바이트

    return 0;
}

// 1
// 4
// 5


  • 이제 정말 멤버의 위치가 위의 그림처럼 되었는지 확인해 보자.


#include <stdio.h>
#include <stddef.h> // offsetof 매크로가 정의된 헤더 파일

#pragma pack(push, 1) // 1바이트 크기로 정렬
struct PacketHeader
{
    char flags; // 1바이트
    int seq;    // 4바이트
};
#pragma pack(pop) // 정렬 설정을 이전 상태(기본값)로 되돌림

int main()
{
    printf("%d\n", offsetof(struct PacketHeader, flags));
    printf("%d\n", offsetof(struct PacketHeader, seq));

    return 0;
}

// 0
// 1


  • 구조체를 1바이트 단위로 정렬했으므로 seq의 상대 위치는 1이 나온다.
  • 즉, 자료형 크기 그대로 정렬되기 때문에 char flags 바로 뒤에 int seq가 와서 1이다.


  • #pragma pack(push, 1), __attribute__((aligned(1), packed))에는 1이외에도 2, 4, 8, 16도 지정할 수 있다.
  • 다음 그림은 2바이트와 4바이트 단위로 구조체를 정렬한 모양이다.


003


  • 만약 이 상태에서 8, 16바이트로 구조체를 정렬하더라도 구조체의 크기는 8이 나올 것이다.
  • 왜냐하면 구조체 안에서 가장 큰 자료형의 크기(4)보다 정렬 설정 크기(8, 16)가 크기 때문이다.
  • 보통 #pragma pack(push, 1)을 주로 사용하므로 신경쓰지 않아도 된다.

References