Q & A

sizeof로 구조체의 크기를 구했더니 제가 정의한 구조체의 크기보다 큰 값을 반환합니다.

먼저 다음과 같은 구조체가 있습니다.

struct Data {
    char mode;    // 1바이트
    int count;    // 4바이트
};

char는 1바이트 int는 4바이트이므로 5바이트일 것 같지만 sizeof는 8바이트를 반환합니다. C 언어 컴파일러는 구조체를 가장 큰 자료형 크기의 배수로 정렬하므로 여기서는 int의 크기 4바이트로 정렬하여 char 뒤에는 패딩 3바이트가 들어가고 전체 크기는 8바이트가 됩니다.

그림 구조체 멤버 정렬

구조체 정렬을 했을 때 멤버의 순서 또는 패딩의 위치가 바뀔 수 있나요?

다음과 같이 charint 멤버를 가지는 구조체 있습니다.

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

이 구조체를 4바이트 단위로 정렬했을 때 1, 3, 4 말고도 3, 1, 4 크기로 배치되는 일은 없을까요? 혹은 char와 int의 순서가 바뀌어서 4, 1, 3이나 4, 3, 1 크기가 되는 일은 없을까요?

그림 나올 수 없는 구조체 정렬 모양

컴파일러는 구조체를 정렬할 때 항상 멤버 뒤에 패딩을 넣으며 멤버의 순서를 바꾸지 않습니다. 따라서 구조체 Data는 4바이트 단위로 정렬하면 1, 3, 4 크기로 배치됩니다.

그럼 char, int라면 1, 1, 4 크기로 6바이트 정렬은 안될까요? 이렇게 되면 32비트 CPU에서 2바이트 부분을 읽을 때 비효율적인 모양이 됩니다(32비트 CPU는 4바이트일 때 가장 효율적). 마찬가지로 64비트 CPU에서 읽을 때도 전체 크기 6바이트는 비효율적입니다(64비트 CPU는 8바이트일 때 가장 효율적).

보통 가장 큰 자료형 크기의 배수로 정렬하면 대부분의 경우 4의 배수로 만들어지기 때문에 가장 큰 자료형을 기준으로 삼습니다. 예를 들어 char만 모여있거나 short만 모여있으면 어쩔 수 없지만 int가 섞여있다면 4의 배수, long long이 섞여있다면 8의 배수로 정렬하므로 효율적인 모양이 됩니다.

CPU 비트 수에 맞게 최적화 하는 방법이 있나요?

char만 모여있거나 short만 모여있는 구조체는 일부러 int나 long long 멤버를 넣어서 CPU에 최적화를 하기도 합니다. 다음과 같이 char만 모여있는 구조체에 int 멤버를 넣으면 4바이트 단위로 정렬되므로 32비트 CPU에 최적화됩니다.

struct Data {
    char mode;        // 사용하는 멤버
    char count;       // 사용하는 멤버
    int alignment;    // 사용하지는 않지만 구조체를 4바이트 단위로 정렬하기 위한 멤버
};

마찬가지로 short만 모여있는 구조체에 long long 멤버를 넣으면 8바이트 단위로 정렬되므로 64비트 CPU에 최적화됩니다.

struct Data {
    short mode;             // 사용하는 멤버
    short count;            // 사용하는 멤버
    long long alignment;    // 사용하지는 않지만 구조체를 8바이트 단위로 정렬하기 위한 멤버
};

여기서 alignment 멤버는 실제로 사용하지는 않지만 컴파일러가 구조체를 4 또는 8바이트로 정렬하도록 알려주는 역할을 합니다. 물론 멤버를 추가하지 않고 구조체 정렬 크기를 조절해도 됩니다.

size_t 자료형은 무엇인가요?

size_t은 부호 없는 정수 자료형인데 sizeof 연산자나 offsetof 매크로의 결과가 size_t입니다.

#include <stdio.h>
#include <stddef.h>

struct Data {
    char mode;
    int count;
};

int main()
{
    size_t size = sizeof(int);
    size_t offset = offsetof(struct Data, count);

    // size_t를 출력할 때는 서식 지정자에 z를 붙임
    printf("%zd %zd\n", size, offset);

    return 0;
}

sizeof 연산자와 offsetof 매크로의 결과, size_t형 변수를 출력할 때는 서식 지정자에 z를 붙입니다(size의 z). 보통은 z를 붙이지 않고 %d로 출력하는데 C 언어 표준을 엄격하게 따르자면 %zd가 맞습니다(8진수는 %zo, 16진수는 %zx).

size_t는 32비트에서 4바이트, 64비트에서 8바이트로 정의되어 있습니다. 다음은 Visual Studio에서 size_t를 정의한 부분인데 __int64는 Visual Studio에서 제공하는 8바이트 정수 자료형입니다.

#ifdef _WIN64    // 64비트일 때
    typedef unsigned __int64 size_t;    // 8바이트 크기의 부호 없는 정수 자료형
#else            // 64비트가 아닐 때
    typedef unsigned int     size_t;    // 4바이트 크기의 부호 없는 정수 자료형
#endif

C 언어 표준 함수에서도 크기를 의미하는 매개변수나 반환값은 size_t를 사용하고 있고, CPU 비트 수에 맞게 자료형을 제공하고 있으므로 크기를 의미하는 변수는 unsigned int 대신 size_t로 선언하는 것이 좋습니다.