44. 구조체 멤버 정렬
1. 키워드
- 구조체 멤버 정렬
- 패딩(Padding)
2. 구조체 멤버 정렬 사용하기
- 구조체가 메모리에 올라갔을 때 멤버를 정렬하는 기능에 대해 알아보자.
- 컴퓨터에서 CPU가 메모리에 접근할 때 32비트 CPU는 4바이트 단위, 64비트 CPU는 8바이트 단위로 접근한다.
- 만약 32비트 CPU에서 4바이트보다 작은 데이터에 접근할 경우 내부적으로 시프트 연산이 발생해서 효율이 떨어진다.
- 그래서 C 컴파일러는 CPU가 메모리의 데이터에 효율적으로 접근할 수 있도록 구조체를 일정한 크기로 정렬을 하게 된다.
- 예를 들어 구조체 크기가 15나 17바이트가 되면 접근 효율이 떨어지게 되므로 2, 4, 8, 16바이트 단위로 정렬을 하는 것이다.
3. 구조체 크기 알아보기
- 구조체 정렬을 하기 전에 먼저 구조체의 크기부터 알아보자.
- 구조체의 전체 크기는
sizeof
연산자를 사용하면 알 수 있다.
- 이제 가상의 네트워크 패킷 구조체
PacketHeader
를 정의해서 멤버의 크기와 구조체의 크기를 구해 보자.
#include <stdio.h>
struct PacketHeader
{
char flags; // 1바이트
int seq; // 4바이트
};
int main()
{
struct PacketHeader header;
printf("%d\n", sizeof(header.flags)); // char 타입은 1바이트
printf("%d\n", sizeof(header.seq)); // int 타입은 4바이트
printf("%d\n", sizeof(header)); // 구조체 전체 크기는 8바이트
printf("%d\n", sizeof(struct PacketHeader)); // 구조체 이름으로 크기 구하기
return 0;
}
// 1
// 4
// 8
// 8
- 구조체의 크기를 구하려면
sizeof(header)
처럼sizeof
연산자 안에 변수를 넣어주거나sizeof(struct PacketHeader)
처럼struct
키워드와 구조체 이름을 넣어주면 된다. PacketHeader
구조체 안에는 1바이트 크기의char
타입과 4바이트 크기의int
타입이 들어있다.- 그래서 전체 크기는 5바이트가 나와야 할 것 같은데 실제로는 8바이트가 나왔다.
- C에서는 구조체를 정렬할 때 멤버 중에서 가장 큰 자료형 크기의 배수로 정렬한다.
- 여기서 가장 큰 자료형은
int
타입이므로,int
타입의 크기 4바이트를 기준으로 정렬한다.
- 4바이트로 정렬해서
flags
와seq
가 모두 들어가는 최소 크기는 8바이트이다. - 따라서 구조체 정렬을 하면 5바이트가 아닌 8바이트가 된다.
- 여기서는 1바이트 크기의
char flags
뒤에는 4바이트를 맞추기 위해 남는 공간에 3바이트가 더 들어간다. - 이렇게 구조체를 정렬할 때 남는 공간을 패딩이라고 부른다.
- 그럼 정말 구조체를 정렬한 뒤 멤버의 위치가 위의 그림처럼 되었는지 확인해 보자.
stddef.h
헤더 파일에 정의되어 있는offsetof
매크로를 사용하면 구조체에서 멤버의 위치를 구할 수 있다.
#include <stdio.h>
#include <stddef.h> // offsetof 매크로가 정의된 헤더 파일
struct PacketHeader
{
char flags; // 1바이트
int seq; // 4바이트
};
int main()
{
printf("%d\n", offsetof(struct PacketHeader, flags));
printf("%d\n", offsetof(struct PacketHeader, seq));
return 0;
}
// 0
// 4
offsetof
매크로에 구조체와 멤버를 지정하면 구조체에서 해당 멤버의 상대 위치가 반환된다.- 첫 멤버의 상대 위치는
0
이다. - 여기서는 구조체가 4바이트 단위로 정렬하므로
seq
의 위치는1
이 아닌4
가 나온다.
4. 구조체 정렬 크기 조절하기
- C에서는 구조체를 정렬하는 표준 방법이 없다.
- 하지만 각 컴파일러에서 제공하는 특별한 지시자를 사용하면 구조체 정렬 크기를 조절할 수 있다.
1] Visual Studio, GCC 4.0 이상
2] GCC 4.0 미만
- 다음 내용을 입력한 뒤 실행해 보자.
#include <stdio.h>
#pragma pack(push, 1) // 1바이트 크기로 정렬
struct PacketHeader
{
char flags; // 1바이트
int seq; // 4바이트
};
#pragma pack(pop) // 정렬 설정을 이전 상태(기본값)로 되돌림
int main()
{
struct PacketHeader header;
printf("%d\n", sizeof(header.flags)); // char 타입은 1바이트
printf("%d\n", sizeof(header.seq)); // int 타입은 4바이트
printf("%d\n", sizeof(header)); // 1바이트 단위로 정렬했으므로
// 구조체 전체 크기는 5바이트
return 0;
}
// 1
// 4
// 5
- 구조체를 정의할 때 위 아래로
#pragma pack(push, 1)
과#pragma pack(pop)
을 넣어줬다. - 여기서 중요한 부분은
pack
안의1
이다. - C에서 자료형의 크기는 바이트 단위이고 가장 작은 크기는 1바이트이다.
- 따라서
pack
을1
로 설정하면 1바이트 단위로 정렬하게 되므로 남는 공간이 없이 자료형 크기를 그대로 메모리에 올라간다.
#pragma pack(push, 1)
을 한 번 사용하면 그 아래에 오는 모든 구조체에 영향을 주므로 정렬 설정을 한 뒤에는#pragma pack(pop)
을 사용하여 설정을 이전 상태로 되돌린다.
- 만약 GCC 버전이 4.0 미만이라면
#pragma pack(push, 1)
,#pragma pack(pop)
대신__attribute__((aligned(1), packed))
를 사용한다.
#include <stdio.h>
struct PacketHeader
{
char flags; // 1바이트
int seq; // 4바이트
} __attribute__((aligned(1), packed)); // GCC 4.0 미만. 1바이트 크기로 정렬
int main()
{
struct PacketHeader header;
printf("%d\n", sizeof(header.flags)); // char 타입은 1바이트
printf("%d\n", sizeof(header.seq)); // int 타입은 4바이트
printf("%d\n", sizeof(header)); // 1바이트 단위로 정렬했으므로
// 구조체 전체 크기는 5바이트
return 0;
}
// 1
// 4
// 5
- 이제 정말 멤버의 위치가 위의 그림처럼 되었는지 확인해 보자.
#include <stdio.h>
#include <stddef.h> // offsetof 매크로가 정의된 헤더 파일
#pragma pack(push, 1) // 1바이트 크기로 정렬
struct PacketHeader
{
char flags; // 1바이트
int seq; // 4바이트
};
#pragma pack(pop) // 정렬 설정을 이전 상태(기본값)로 되돌림
int main()
{
printf("%d\n", offsetof(struct PacketHeader, flags));
printf("%d\n", offsetof(struct PacketHeader, seq));
return 0;
}
// 0
// 1
- 구조체를 1바이트 단위로 정렬했으므로
seq
의 상대 위치는1
이 나온다. - 즉, 자료형 크기 그대로 정렬되기 때문에
char flags
바로 뒤에int seq
가 와서1
이다.
#pragma pack(push, 1)
,__attribute__((aligned(1), packed))
에는1
이외에도2
,4
,8
,16
도 지정할 수 있다.- 다음 그림은 2바이트와 4바이트 단위로 구조체를 정렬한 모양이다.
- 만약 이 상태에서 8, 16바이트로 구조체를 정렬하더라도 구조체의 크기는 8이 나올 것이다.
- 왜냐하면 구조체 안에서 가장 큰 자료형의 크기(
4
)보다 정렬 설정 크기(8
,16
)가 크기 때문이다. - 보통
#pragma pack(push, 1)
을 주로 사용하므로 신경쓰지 않아도 된다.