[C]구조체(struct)[2] - 패딩


  • 이번 포스트는 구조체의 패딩에 관한 내용입니다.

1️⃣ 패딩이란?

패딩이란 구조체 멤버변수들을 메모리에서 CPU로 읽을 각 시스템의 워드(word) 경계에서 읽어오는 것이 효율적이기 때문에 컴파일러가 (성능상의 이유로)레지스터의 블록에 맞춰 최적화 하는 작업입니다.


2️⃣ 구조체에 패딩이 생기는 과정

< 다양한 자료형을 가진 구조체의 메모리 >

#include <stdio.h>

typedef struct {
    int num[6];
} info_t;    // 24bytes

typedef struct {
    unsigned int   id;       // 4bytes
    info_t        info;     // 24bytes
    unsigned short height;    // 2bytes
    float         weight;   // 4bytes
    unsigned short age;      // 2bytes
} data_t;

int main(void)
{
    info_t info;
    data_t data;

    printf("info size: %d\ndata size: %d\n", sizeof(info), sizeof(data));
}
/*---출력---*/
info size: 24
data size: 40

위의 data_t구조체의 경우 각멤버변수 크기의 합인 36bytes가 나와야 정상이지만 40bytes로 크기가 잡히는 것을 확인할 수 있습니다.
구조체의 멤버변수들의 주소연속적으로 존재함을 이용해서 각 요소의 주소값의 비교해 보았습니다.

< 각요소의 주소값을 비교 >

int main(void)
{
    data_t data;

    int off_id = (char*)&data.id - (char*)&data;
    int off_info = (char*)&data.info - (char*)&data;
    int off_height = (char*)&data.height - (char*)&data;
    int off_weight = (char*)&data.weight - (char*)&data;
    int off_age = (char*)&data.age - (char*)&data;

    printf("off_id: %d\n", off_id);
    printf("off_info: %d\n", off_info);
    printf("off_height: %d\n", off_height);
    printf("off_weight: %d\n", off_weight);
    printf("off_age: %d\n", off_age);
}
/*---출력---*/
off_id: 0
off_info: 4
off_height: 28    //height는 2bytes인데??
off_weight: 32
off_age: 36

위와 같이 각 요소의 주소값을 비교해보니 height의 크기가 2bytes인데 4bytes로 잡혀있음을 알 수 있습니다. 이와 동일하게 age도 2bytes크기지만 4bytes로 잡혀 있습니다.

padding_img1

  • 메모리를 읽어올 때 각 시스템의 워드(word) 경계에서 읽어오는 것이 효율적이기 때문에 이런식으로 컴파일러가 패딩을 넣어줍니다. 다른 말로 정렬한다(aligned)라고 합니다.
  • 이런식으로 바이트 정렬 요구사항 때문에 구멍이 생기게 되는데 시스템마다 메모리에 접근할 때 사용하는 주소에 대한 요구사항이 다릅니다.
  • x86시스템4바이트(워드크기)경계에서 읽어오는게 효율적입니다.
    (4바이트 경계에 정렬됩니다.(aligned))
  • 요구사항이 다른 아키텍처에서 호환을 시키기 위해서는 패딩을 줄이는 것이 좋을 것 같습니다.

3️⃣ 패딩 줄이기

(방법1) 맴버변수를 재배열

< 기존 구조체 >

typedef struct {
    unsigned int   id;       // 4bytes
    info_t        info;     // 24bytes
    unsigned short height;    // 2bytes
    float         weight;   // 4bytes
    unsigned short age;      // 2bytes
} data_t;

< 바이트에 맞게 재배열한 구조체 >

typedef struct {
    unsigned int   id;       // 4bytes
    info_t        info;     // 24bytes
    unsigned short height;    // 2bytes
    unsigned short age;      // 2bytes
    float         weight;   // 4bytes  
} data_t;

padding_img2

  • 이런식으로 재배열하는 것으로도 패딩이 없어졌습니다.(2bytes의 short변수 2개4bytes로 합체 되었습니다.)
  • 하지만 이런식으로 일리리 4바이트 경계를 살피는 것은 다소 귀찮을 수 있습니다.

(방법2) "#pragma pack"을 사용

#pragma pack(push, 1)
typedef struct {
    unsigned int   id;       // 4bytes
    info_t        info;     // 24bytes
    unsigned short height;    // 2bytes
    float         weight;   // 4bytes
    unsigned short age;      // 2bytes
} data_t;
#pragma pack(pop)
/*--사이즈--*/
sizeof(data_t) = 36
  • #pragma pack을 사용하면 패딩없이 36의 크기로 메모리가 잡힙니다.
  • 하지만 C표준 문법이 아닙니다. 요즘 나온 컴파일러들이 지원해주는 기능입니다.
    (표준이 아니기에 모든 플랫폼에서 공통으로 호환하지 않습니다.)

(방법3) 패딩을 명시적으로 삽입

어쩔 수없이 패딩이 생길 거라면 구조체에 패딩을 명시적으로 넣는 것이 좋습니다.

typedef struct {
    unsigned int   id;
    info_t        info;
    unsigned short height;
    char         unused[2];  //패딩을 명시적으로 삽입
    float         weight;
} data_t;

< assert()를 사용한 구조체 크기를 확인 >

#include <assert.h>
assert(sizeof(data_t) == 36);
  • 디버그모드에서 assert함수로 패딩을 잡아낼 수 있습니다.
  • 특히, 바이트 단위로 저장해서 다른 실행파일과 공유를 해야할 때 위와같이 구조체의 크기를 체크하는 것이 좋습니다.




© 2021.02. by kirim

Powered by kkrim