구조체 메모리 크기와 정렬, 패딩 (Struct Alignment)

한눈에 보기

구조체 크기가 멤버 합보다 커지는 이유인 메모리 정렬과 패딩의 규칙을 오프셋 계산 예시로 설명하고, 멤버 순서에 따른 크기 차이와 #pragma pack 사용 시 주의점을 다룬다.

최종 업데이트 2026.09.17

구조체(Struct)의 크기는 멤버 크기의 단순 합이 아니라, 컴파일러가 메모리 정렬(Alignment)을 맞추기 위해 삽입하는 패딩(Padding)까지 더한 값이다. CPU가 데이터를 효율적으로 읽으려면 각 타입이 자신의 크기 배수 주소에 놓여야 하기 때문에, 멤버 선언 순서에 따라 같은 멤버 구성이라도 구조체 크기가 달라진다. 이 원리를 알면 sizeof 결과를 정확히 예측하고 메모리를 아끼는 멤버 배치를 설계할 수 있다.

메모리 정렬이 필요한 이유

CPU는 메모리를 1바이트씩이 아니라 워드(Word) 단위로 읽는다. 4바이트 int가 4의 배수 주소에 있으면 한 번의 접근으로 읽을 수 있지만, 어중간한 주소에 걸쳐 있으면 두 번 읽어서 조합해야 하거나, 일부 아키텍처에서는 아예 정렬 오류로 프로그램이 중단된다. 그래서 각 타입에는 정렬 요구사항(Alignment Requirement)이 있으며 보통 타입 크기와 같다. char는 1, short는 2, int와 float는 4, double과 64비트 환경의 포인터는 8이다. 컴파일러는 이 요구를 만족시키기 위해 멤버 사이에 빈 공간을 끼워 넣는데 이것이 패딩이다.

패딩이 삽입되는 규칙

규칙은 두 가지로 정리된다. 첫째, 각 멤버는 자신의 정렬 요구사항의 배수 오프셋(Offset)에 배치되며, 그 앞에 필요한 만큼 내부 패딩이 들어간다. 둘째, 구조체 전체 크기는 멤버 중 가장 큰 정렬 요구사항의 배수가 되도록 끝에 후행 패딩이 붙는다. 후행 패딩은 구조체 배열을 만들었을 때 두 번째 원소부터도 정렬이 유지되도록 하기 위한 것이다.

#include <stdio.h>

struct A {          /* 총 12바이트 */
    char c;         /* 오프셋 0 */
                    /* 패딩 3바이트 */
    int  i;         /* 오프셋 4 */
    char d;         /* 오프셋 8 */
                    /* 후행 패딩 3바이트 */
};

struct B {          /* 총 8바이트 */
    int  i;         /* 오프셋 0 */
    char c;         /* 오프셋 4 */
    char d;         /* 오프셋 5 */
                    /* 후행 패딩 2바이트 */
};

int main(void) {
    printf("%zu %zu\n", sizeof(struct A), sizeof(struct B));
    return 0;
}

두 구조체는 int 하나와 char 두 개라는 같은 멤버를 가지지만, 선언 순서 때문에 A는 12바이트, B는 8바이트가 된다. 큰 타입부터 작은 타입 순으로 배치하면 패딩이 줄어드는 경향이 있으며, 대량으로 배열에 담기는 구조체라면 이 차이가 전체 메모리 사용량과 캐시(Cache) 효율에 직접 영향을 준다.

크기 계산 연습

구조체 멤버배치(64비트 기준)sizeof
char, double, int0, 패딩 7, 8, 16, 후행 패딩 424
double, int, char0, 8, 12, 후행 패딩 316
char, char, short, int0, 1, 2, 48
char만 3개0, 1, 23

가장 큰 멤버가 double이면 전체 크기는 8의 배수, int가 최대면 4의 배수가 된다. 멤버가 char뿐이면 정렬 요구사항이 1이므로 패딩이 전혀 없다. 특정 멤버의 오프셋은 stddef.h의 offsetof 매크로로 확인할 수 있고, C11부터는 _Alignof 연산자(C++에서는 alignof)로 타입의 정렬 요구사항 자체를 조회할 수 있다.

패딩 제어와 주의할 점

네트워크 패킷 헤더나 파일 포맷처럼 바이트 배치가 규격으로 고정된 데이터를 다룰 때는 패딩이 방해가 된다. 이때 #pragma pack(1) 같은 지시문으로 패딩을 없앨 수 있지만, 정렬되지 않은 접근으로 인한 성능 저하나 일부 플랫폼에서의 오류를 감수해야 하므로 꼭 필요한 구간에만 제한적으로 쓴다. 패딩 바이트는 값이 보장되지 않는 쓰레기 공간이므로 memcmp로 구조체 전체를 비교하면 논리적으로 같은 두 구조체가 다르다고 판정될 수 있다는 점도 주의해야 한다. 구조체를 그대로 파일이나 소켓에 write하는 코드가 플랫폼 간에 호환되지 않는 이유도, 컴파일러와 아키텍처마다 패딩 배치가 다를 수 있기 때문이다. 직렬화(Serialization)는 멤버 단위로 명시적으로 하는 것이 안전하다.

C++에서도 같은 규칙이 적용되며, 가상 함수가 있는 클래스는 가상 함수 테이블 포인터(vptr)가 숨은 멤버로 추가되어 크기가 포인터 하나만큼 더 커진다. 또한 멤버가 하나도 없는 빈 구조체도 크기가 0이 아니라 1인데, 서로 다른 객체가 서로 다른 주소를 가져야 한다는 규칙 때문이다.

면접에서 이렇게 물어본다

  • char, int, char 순서로 멤버를 가진 구조체의 크기를 계산 과정과 함께 설명해 보라.
  • 패딩은 왜 존재하며, CPU 입장에서 정렬된 접근이 왜 유리한가?
  • 멤버 순서만 바꿔서 구조체 크기를 줄일 수 있는 이유는 무엇인가?
  • 구조체 전체 크기가 가장 큰 멤버의 정렬 요구사항 배수가 되어야 하는 이유를 배열 관점에서 설명해 보라.
  • #pragma pack(1)은 언제 쓰며 어떤 부작용이 있는가?
C / C++ 문서 전체 보기

이 개념, 내 코드에서는 어떻게 물어볼까요

데브핏(DevFit)에 채용공고 URL을 붙여 넣고 레포지토리를 연결하면, 내가 쓴 커밋에서 예상 질문과 어필 포인트를 만듭니다. 하루 10회까지 무료입니다. 합불 판정이나 지원자 랭킹은 만들지 않고, 최종 판단은 사람이 합니다.

공고 넣고 시작하기