Notice
Recent Posts
Recent Comments
Link
«   2025/07   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

문타쿠, 공부하다.

[C언어 코딩 도장] Unit 51. 구조체 멤버 정렬 사용하기 본문

C언어/C언어 코딩 도장

[C언어 코딩 도장] Unit 51. 구조체 멤버 정렬 사용하기

개발새발 문타쿠 2023. 10. 29. 02:45

INTRO

컴퓨터의 CPU가 메모리에 접근할 때 32비트 CPU는 4바이트 단위, 64비트 CPU는 8바이트 단위로 접근한다.

만약 32비트 CPU에서 4바이트보다 작은 데이터에 접근하게 될 경우 내부적으로 시프트 연산이 발생해서 효율이 떨어지게 되는데...

 

그래서 C언어 컴파일러는 구조체가 메모리에 올라갔을 때,  CPU가 메모리의 데이터에 효율적으로 접근할 수 있도록 구조체를 일정한 크기로 정렬한다. => 구조체 멤버 정렬


51.1 구조체 크기 알아보기

#include <stdio.h>

struct PacketHeader
{
    char flags;
    int seq;
};

int main(void)
{
    struct PacketHeader header;

    printf("구조체 크기: %zd\n", sizeof(header));                // 구조체 변수로 크기 구하기
    printf("구조체 크기: %zd\n", sizeof(struct PacketHeader));   // 구조체 이름으로 크기 구하기
    printf("%zd\n", sizeof(header.flags));
    printf("%zd\n", sizeof(header.seq));
    
    return 0;
}

PacketHeader 구조체 안에는 1바이트 크기의 char와 4바이트 크기의 int가 들어가 있다.

그러면 전체 구조체 크기는 5바이트가 나와야 할 것 같은데 왜 8바이트 나온 것일까?

C언어에서는 구조체를 정렬할 때 멤버 중에서 가장 큰 자료형 크기의 배수로 정렬을 한다.

 

위의 코드에서 가장 큰 자료형은 int이고, int의 크기 4바이트를 기준으로 정렬하게되면, flag와 seq가 모두 들어가는 구조체의 최소 크기는 8바이트가 된다.

 

이때 1바이트인 char flags 뒤에는 기준인 4바이트를 맞추기 위해 남는 공간 3바이트가 더 들어가게 되는데, 이렇게 구조체를 정렬할 때 남는 공간을 채우는 것을 패딩(padding)이라고 한다.


구조체를 정렬한 뒤 멤버의 위치가 위의 그림처럼 되는지 확인해보자.

구조체에서 멤버의 위치를 구할 때는 offsetof 매크로를 사용하며, 이는 stddef.h 헤더 파일에 정의되어 있다.

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

struct PacketHeader
{
    char flags;
    int seq;
};

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

위와 같이 offsetof 매크로에 구조체와 멤버를 지정하면 구조체에서 해당 멤버의 상대 위치가 반환된다.

(첫 멤버의 상대위치는 0)

 

그래서 flags의 위치는 0이고, seq의 위치는 패딩 공간의 위치(1, 2, 3)를 지나 4가 나온다.


51.2 구조체 정렬 크기 조절하기

데이터 전송이나 저장 시 구조체 정렬을 피해야 하는 경우가 있는데, 이를 피하려면 어떻게 해야 할까?

-> 각 컴파일러에서 제공하는 특별한 지시자를 사용하면 구조체 정렬 크기를 조절할 수 있다.

#include <stdio.h>

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

int main(void)
{
    struct PacketHeader header;

    printf("구조체 크기: %zd\n", sizeof(header));               
    printf("%zd\n", sizeof(header.flags));
    printf("%zd\n", sizeof(header.seq));
    
    return 0;
}

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


위의 코드에 대한 위치도 한 번 확인해보자.

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

#pragma pack(push, 1) 
struct PacketHeader
{
    char flags;
    int seq;
};
#pragma pack(pop)

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

구조체를 1바이트로 정렬하는 것은 구조체의 내용을 파일에 쓰거나 네트워크로 전송 할 때 꼭 필요하다는 점을 알아두자.


다음은 위의 코드를 2바이트( #pragma pack(push, 2) )와 4바이트( #pragma pack(push, 4) ) 크기로 구조체를 정렬했을 때의 모양이다.

만약 이 상태에서 8바이트나 16바이트로 구조체를 정렬하더라도 구조체의 크기는 8이 나온다.

 

왜냐하면 C언어에서는 구조체를 정렬할 때 멤버 중에서 가장 큰 자료형 크기의 배수로 정렬을 하는데 위의 구조체 안에서 가장 큰 자료형 int의 크기 4바이트보다 정렬 설정의 크기가 크기 때문이다.