#pragma pack의 사용이유와 사용방법에 대해서 알아볼 것이다.
먼저 일반적인 구조체 선언 코드를 보자.
#include<stdio.h>
typedef struct test{
char a; // 1byte
int b; // 4byte
}test;
이때 구조체 변수 a는 char형이므로 1byte일 것이고, 변수 b는 int형으로 4byte일 것이다.
하지만 구조체의 크기 할당에는 크기가 가장 큰 자료형을 기준으로 배수만큼 커지게 된다.
실제로 구조체의 크기를 출력하는 코드를 통해서 직접 확인해보자.
<예제 코드1>
#include<stdio.h>
typedef struct test{
char a;
int b;
}test;
int main(){
test s1;
printf("s1.a size : %d byte\n",sizeof(s1.a));
printf("s1.b size : %d byte\n",sizeof(s1.b));
printf("s1 size : %d byte\n",sizeof(s1));
return 0;
}
<실행 결과1>
결과를 보면, 구조체 변수 a와 b로 접근했을 때는 각각의 바이트가 잘 할당된 것으로 보일 수 있으나, 구조체의 크기를 찍어보면 8byte 크기가 할당되었음을 알 수 있다. 이는 구조체의 변수 중에서 가장 크기가 큰 자료형인 int의 4바이트를 기준으로 할당되었기 때문이다. 구조체에서 실제 필요한 변수의 크기는 총 5byte지만, 3byte의 메모리 낭비가 생긴다.
낭비가 생기는 것을 알면서도 컴파일러가 이렇게 처리하는 이유는 CPU가 접근하기 쉬운 위치에 필드를 배치하기 때문에 4바이트 또는 8바이트 단위로 끊는 바이트 패딩 작업을 수행하는 것이며, 이는 성능과도 연결된다. 이때 중간중간 빈 공간(패딩비트)가 생긴다.
만약 각각의 컴퓨터 시스템이 다르고, 각자에게 맞는 단위로 바이트 패딩을 한다면 네트워크 통신과정에서 값들이 이상한 값으로 인식(패딩된 크기가 다르기 때문에)될 수 있다. 그래서 네트워크 통신을 할때는 패킷의 사이즈를 정확하게 지정해야하고, 이 문제를 #pragma pack을 이용하여 해결하는 것이다.
그래서 네트워크 헤더 구조체 같은 경우에는 필수적으로 #pragma pack을 사용한다.
구조체의 크기가 커지는 것을 #pragma pack으로 막을 수 있다.
#pragma pack(push, n)
#pragma pack(pop)
#이 붙어있음으로 전처리기라는 것을 알 수 있고, n은 정렬크기를 넣어주면 된다.
n값은 1, 2, 4, 8, 16 만 유효하며, 디폴트 값은 기본 타입중에 가장 큰 타입인 8이다.
<예제 코드2>
#include<stdio.h>
#pragma pack(push,1)
typedef struct test{
char a;
int b;
}test;
int main(){
test s1;
printf("s1.a size : %d byte\n",sizeof(s1.a));
printf("s1.b size : %d byte\n",sizeof(s1.b));
printf("s1 size : %d byte\n",sizeof(s1));
return 0;
}
2번째 줄에 #pragma pack(push,1)을 추가했고, 이는 1byte씩 정렬하겠다고 선언한 것이다.
<예제 결과2>
s1 구조체의 size를 보면 <예제1>과 다르게 5byte가 할당된 것을 볼 수 있다. 메모리 낭비가 생기지 않는다.
<예제 코드3>
#include<stdio.h>
#pragma pack(push,2)
typedef struct test{
char a;
int b;
}test;
int main(){
test s1;
printf("s1.a size : %d byte\n",sizeof(s1.a));
printf("s1.b size : %d byte\n",sizeof(s1.b));
printf("s1 size : %d byte\n",sizeof(s1));
return 0;
}
이번 예제는 전 예제와 동일하지만, 정렬크기를 2로 주었을 때이다.
<예제 결과3>
특정 구조체에 대해서만 구조체의 정렬크기를 지정해줄 수 있다. 이때 #pragma pack(pop)을 사용한다.
<예제 코드4>
#include<stdio.h>
#pragma pack(push,1)
typedef struct test{
char a;
int b;
}test;
#pragma pack(pop)
typedef struct test2{
char a;
int b;
}test2;
int main(){
test s1;
printf("s1.a size : %d byte\n",sizeof(s1.a));
printf("s1.b size : %d byte\n",sizeof(s1.b));
printf("s1 size : %d byte\n",sizeof(s1));
test2 s2;
printf("s2.a size : %d byte\n",sizeof(s2.a));
printf("s2.b size : %d byte\n",sizeof(s2.b));
printf("s2 size : %d byte\n",sizeof(s2));
return 0;
}
<예제 결과4>
test구조체와 test2구조체의 각 변수들의 구성은 같다. test구조체는 정렬크기를 1로 지정했고, 마지막에 pragma pack(pop)을 사용했다.
출력 결과를 확인해보면 test의 구조체를 받은 s1은 정렬크기가 1이므로 총 크기가 5인 것을 확인할 수 있고, test2의 구조체를 받은 s2는 총 크기가 8인 것을 확인할 수 있다.
같은 변수들을 가진 구조체지만 pragma로 인해 총 할당되는 크기가 다른 것이다.
<예제 코드5>
#include<stdio.h>
typedef struct test{
char a;
short b;
}test;
int main(){
test s1;
printf("s1.a size : %d byte\n",sizeof(s1.a));
printf("s1.b size : %d byte\n",sizeof(s1.b));
printf("s1 size : %d byte\n",sizeof(s1));
return 0;
}
마지막으로 pragma pack의 개념과 구조체의 크기할당에 대한 이해가 생겼다면, 코드 속의 s1 size의 크기를 우리는 알 수 있을 것이다. 이 예제 코드는 pragma pack()을 사용하지 않았다.
<코드 결과5>
* linux 환경일 경우
typedef struct test{
char a;
int b;
}__attribute__((packed)) test;
구조체 선언 시, __attribute__((packed))을 사용한다.
<예제 코드6>
#include<stdio.h>
typedef struct test{
char a;
int b;
} __attribute__((packed))test;
int main(){
test s1;
printf("s1.a size : %ld byte\n",sizeof(s1.a));
printf("s1.b size : %ld byte\n",sizeof(s1.b));
printf("s1 size : %ld byte\n",sizeof(s1));
return 0;
}
<코드 결과6>