C구조체와 문자열

구조체 패딩과 정렬

CPU가 정렬된 주소를 요구하기 때문에 컴파일러가 자동으로 삽입하는 패딩 바이트, `offsetof`로 실제 오프셋 확인, 필드 순서 재배치로 구조체 크기를 줄이는 방법을 정리합니다.

마지막 수정 2026년 3월 26일

기본 패턴

c
#include <stddef.h>   // offsetof
#include <stdio.h>

struct Padded {
    char  a;   // 1바이트
    int   b;   // 4바이트 — 4배수 정렬 위해 3바이트 패딩 삽입
    char  c;   // 1바이트 — 뒤에 3바이트 패딩 삽입
};

printf("%zu\n", sizeof(struct Padded));          // 12 (1+3+4+1+3)
printf("%zu\n", offsetof(struct Padded, b));     // 4
printf("%zu\n", offsetof(struct Padded, c));     // 8

설명

정렬이 왜 필요한가

CPU는 메모리를 타입 크기의 배수 주소에서 읽을 때 가장 효율적입니다. int(4바이트)는 주소가 4의 배수여야 한 번의 메모리 접근으로 읽힙니다. 정렬되지 않으면 두 번 읽어야 하거나 하드웨어 예외가 발생합니다. 컴파일러는 이 규칙을 만족하기 위해 패딩 바이트를 자동으로 삽입합니다.

text
struct Padded 메모리 레이아웃 (12바이트):
[a:1][pad:3][  b:4  ][c:1][pad:3]
 0    1       4        8    9

각 멤버의 정렬 요구:

  • char: 1바이트 정렬 (어느 주소나 가능)
  • short: 2바이트 정렬
  • int, float: 4바이트 정렬
  • double, 포인터: 8바이트 정렬 (64비트)

offsetof — 실제 오프셋 확인

offsetof(type, member)는 구조체 시작부터 멤버까지의 실제 바이트 오프셋을 컴파일 타임에 반환합니다. 네트워크 프로토콜이나 파일 포맷에서 직렬화할 때 필수입니다.

c
struct Point3D {
    float x;   // offset 0
    float y;   // offset 4
    float z;   // offset 8
};

struct Mixed {
    char   flag;    // offset 0
    double value;   // offset 8 (7바이트 패딩)
    int    count;   // offset 16
};                  // 총 24바이트 (마지막에 4바이트 패딩)

printf("%zu\n", offsetof(struct Mixed, value));  // 8
printf("%zu\n", offsetof(struct Mixed, count));  // 16
printf("%zu\n", sizeof(struct Mixed));           // 24

필드 순서를 바꿔 패딩 최소화

크기가 큰 멤버부터 내림차순으로 배치하면 패딩이 줄어듭니다.

c
// ❌ 비효율적 순서 — 12바이트
struct Bad {
    char   a;   // 1 + 3(패딩)
    int    b;   // 4
    char   c;   // 1 + 3(패딩)
};

// ✅ 큰 것부터 — 8바이트 (패딩 0)
struct Good {
    int    b;   // 4
    char   a;   // 1
    char   c;   // 1
               // 2바이트 패딩 (구조체 배열 정렬용)
};

printf("%zu vs %zu\n",
    sizeof(struct Bad),    // 12
    sizeof(struct Good));  //  8

#pragma pack — 패딩 강제 제거

네트워크 패킷이나 파일 포맷처럼 바이트 정확성이 필요한 경우 패딩을 제거합니다. 하지만 성능 저하와 이식성 문제가 있으므로 꼭 필요한 경우에만 씁니다.

c
#pragma pack(push, 1)   // 1바이트 정렬 강제
struct PacketHeader {
    uint8_t  type;      // 1
    uint16_t length;    // 2 (정렬 안 됨)
    uint32_t checksum;  // 4 (정렬 안 됨)
};
#pragma pack(pop)

printf("%zu\n", sizeof(struct PacketHeader));  // 7 (패딩 없음)

빠른 정리

상황적합한 선택
구조체 실제 크기 확인sizeof(struct T)
멤버 실제 오프셋 확인offsetof(struct T, member)
구조체 크기 최소화큰 멤버부터 내림차순 배치
네트워크/파일 포맷 (패딩 금지)#pragma pack(1) 또는 __attribute__((packed))
정렬 요구 확인_Alignof(T) (C11)

주의할 점

sizeof(struct)가 멤버 크기의 합과 다를 수 있습니다. 직렬화 시 구조체를 통째로 복사하면 패딩 바이트도 함께 전송됩니다.

c
struct Header {
    char  type;      // 1
    int   length;    // 4 — 앞에 3바이트 패딩
};

// ❌ 8바이트 전체 전송 — 3바이트 쓰레기 패딩 포함
write(fd, &hdr, sizeof(struct Header));

// ✅ 필드를 개별적으로 직렬화
uint8_t buf[5];
buf[0] = hdr.type;
memcpy(buf + 1, &hdr.length, sizeof(hdr.length));
write(fd, buf, sizeof(buf));

#pragma pack으로 패딩을 제거하면 비정렬 접근으로 인한 성능 저하 또는 일부 아키텍처에서 버스 오류가 발생할 수 있습니다.

참고 링크

1 sources