数据结构对齐补齐
- 数据对齐
Alignment
- 结构体中不同类型的数据在内存中按照一定的规则排列,而不总是一个挨一个的顺序排放(便于寻址);
- 任何成员变量的地址必须是其对齐参数A的整数倍,这个规则叫做数据对齐;
数据对齐会造成结构体内部不同成员变量之间有空隙;
- 每个成员变量的对齐参数A取值规则:
a=min(n, max(self))
普通类型取#pragma pack(n)
、自身大小,二者最小值;a=min(n, max(sizeof element...))
结构体类型取#pragma pack(n)
、最大子变量,二者最小值;
n为系统对齐参数,一般默认为4,可以通过
#pragma pack()
类宏自定义
- 数据补齐
Completion
- 一个结构体变量的大小必须是C的整数倍,这个规则叫数据补齐;
- 这种补齐可能造成结构体在最后多占用一些浪费的字节;
- 结构体补齐参数C的取值规则:
c=max(A)
即所有成员的对齐参数A的最大值(一定不会超过pack值,试证明之);
分析举例
// x *
// y y
// z *
typedef struct {
char c1; //s=1, a=min(4,1), @+0;对齐参数a=1;
short s; //s=2, a=min(4,2), @+2;对齐参数a=2;
char c2; //s=1, a=min(4,1), @+4;对齐参数a=1;
} ST1; //6 = n*2, c=max(a)==2;补齐参数c=2
// x x y y
// y y y y
// z z z z
// m * * *
typedef struct {
short s; //s=2, a=min(4,2), @+0;对齐参数a=2;
ST1 st1; //s=6, a=min(4,max(1,2,1)), @+2;对齐参数a=2;
int i; //s=4, a=min(4,4), @+8;对齐参数a=4;
char c; //s=1, a=min(4,1), @+12;对齐参数a=1;
} ST7; //16 = n*4, c=max(a)==4;补齐参数c=4;
struct成员对齐,总体补齐
- 普通类型成员,对齐参数A为(自身类型大小和指定对齐参数n)二者的最小值,即
a=min(n, max(self))
; - 结构体类型的成员,对齐参数A为其所有成员对齐参数中的最大值,即
a=min(n, max(sizeof element...))
; - 结构体总长度为补齐参数C的整数倍,补齐参数C为所有成员对齐参数最大值,即
c=max(A)
;
结构体中子变量的顺序会影响结构体的大小,占用空间小的子变量写前边可以节约内存空间;
为什么需要内存对齐?
- CPU对内存的读取是不连续的,而是分成块读取的,块的大小值可以是1,2,4,8,16字节;
- 当读取操作的数据未对齐,则需要两次总线周期来访问内存,因此性能会大打折扣;
- 某些硬件平台只能从规定的地址处取某些特定类型的数据,否则抛出硬件异常;
数据结构对齐补齐演示
// 数据结构对齐补齐演示
#include <stdio.h>
#pragma pack(4)
//假设存储位置都起始于0
#define OFFSET_OF(type, member) ((size_t)(&((type *)0)->member))
#define OO1(t, m1) #m1,OFFSET_OF(t, m1)
#define OO2(t, m1, m2) OO1(t, m1), OO1(t, m2)
#define OO3(t, m1, m2, m3) OO2(t, m1, m2), OO1(t, m3)
#define OO4(t, m1, m2, m3, m4) OO3(t, m1, m2, m3), OO1(t, m4)
#define OO5(t, m1, m2, m3, m4, m5) OO4(t, m1, m2, m3, m4), OO1(t, m5)
#define SHOW_OO1(t, m1) \
printf("\n%s offset: %s:%ld\n", #t,OO1(t,m1))
#define SHOW_OO2(t, m1, m2) \
printf("\n%s offset: %s:%ld, %s:%ld\n", #t,OO2(t,m1,m2))
#define SHOW_OO3(t, m1, m2, m3) \
printf("\n%s offset: %s:%ld, %s:%ld, %s:%ld\n", #t,OO3(t,m1,m2,m3))
#define SHOW_OO4(t, m1, m2, m3, m4) \
printf("\n%s offset: %s:%ld, %s:%ld, %s:%ld, %s:%ld\n", #t,OO4(t,m1,m2,m3,m4))
#define SHOW_OO5(t, m1, m2, m3, m4, m5) \
printf("\n%s offset: %s:%ld, %s:%ld, %s:%ld, %s:%ld, %s:%ld\n", #t,OO5(t,m1,m2,m3,m4,m5))
int main() {
// x x x
typedef struct {
char c1; //s=1, a=1, @+0
char c2; //s=1, a=1, @+1
char c3; //s=1, a=1, @+2
} STC; //3 = n*1, c=max(a)==1
SHOW_OO3(STC, c1, c2, c3); //0,1,2
printf("STC size: %ld\n", (long)sizeof(STC)); //3
// x *
// y y
// y y
typedef struct {
char c; //s=1, a=1, @+0
short s1;//s=2, a=2, @+2
short s2;//s=2, a=2, @+4
} STS; //6 = n*2, c=max(a)==2
SHOW_OO3(STS, c, s1, s2); //0,2,4
printf("STS size: %ld\n", (long)sizeof(STS)); //6
// x x * *
// y y y y
typedef struct {
char str[2]; //s=2, a=min(4,2), @+0;
int i; //s=4, a=min(4,4), @+4;
} ST0; //8 = n*4, c=max(a)==4
SHOW_OO2(ST0, str, i); //0,4
printf("ST0 size: %ld\n", (long)sizeof(ST0)); //8
// x *
// y y
// z *
typedef struct {
char c1; //s=1, a=min(4,1), @+0;
short s; //s=2, a=min(4,2), @+2;
char c2; //s=1, a=min(4,1), @+4;
} ST1; //6 = n*2, c=max(a)==2
SHOW_OO3(ST1, c1, s, c2);//0,2,4
printf("ST1 size: %ld\n", (long)sizeof(ST1)); //6
// x * y y
// z * * *
// m m m m
typedef struct {
char c1; //s=1, a=min(4,1), @+0;
short s; //s=2, a=min(4,2), @+2;
char c2; //s=1, a=min(4,1), @+4;
int i; //s=4, a=min(4,4), @+8;
} ST2; //12 = n*4, c=max(a)==4
SHOW_OO4(ST2, c1, s, c2, i); //0,2,4,8
printf("ST2 size: %ld\n", (long)sizeof(ST2)); //16
// x * y y
// z * * *
// m m m m
// m m m m
typedef struct {
char c1; //s=1, a=min(4,1), @+0;
short s; //s=2, a=min(4,2), @+2;
char c2; //s=1, a=min(4,1), @+4;
int *pi; //s=8, a=min(4,8), @+8;
} ST3; //16 = n*8, c=max(a)==8
SHOW_OO4(ST3, c1, s, c2, pi); //0,2,4,8
printf("ST3 size: %ld\n", (long)sizeof(ST3)); //12
// x x * *
// y y y y
// z m * *
typedef struct {
short s; //s=2, a=min(4,2), @+0;
int i; //s=4, a=min(4,4), @+4;
char c1; //s=1, a=min(4,1), @+8;
char c2; //s=1, a=min(4,1), @+9;
} ST4; //12 = n*4, c=max(a)==4
SHOW_OO4(ST4, s, i, c1, c2); //0,4,8,9
printf("ST4 size: %ld\n", (long)sizeof(ST4)); //12
// x x x x
// x x * *
// y y y y
// z z m *
typedef struct {
ST1 st1; //s=6, a=min(4,max(1,2,1)), @+0;
int i; //s=4, a=min(4,4), @+8;
short s; //s=2, a=min(4,2), @+12;
char c; //s=1, a=min(4,1), @+14;
} ST5; //16 = n*4, c=max(a)==4
SHOW_OO4(ST5, st1, i, s, c); //0,8,12,14
printf("ST5 size: %ld\n", (long)sizeof(ST5)); //16
// x x x x
// x x y y
// z z z z
// m * * *
typedef struct {
ST1 st1; //s=6, a=min(4,max(1,2,1)), @+0;
short s; //s=2, a=min(4,2), @+6;
int i; //s=4, a=min(4,4), @+8;
char c; //s=1, a=min(4,1), @+12;
} ST6; //16 = n*4, c=max(a)==4
SHOW_OO4(ST6, st1, s, i, c); //0,6,8,12
printf("ST6 size: %ld\n", (long)sizeof(ST6)); //16
// x x y y
// y y y y
// z z z z
// m * * *
typedef struct {
short s; //s=2, a=min(4,2), @+0;
ST1 st1; //s=6, a=min(4,max(1,2,1)), @+2;
int i; //s=4, a=min(4,4), @+8;
char c; //s=1, a=min(4,1), @+12;
} ST7; //16 = n*4, c=max(a)==4
SHOW_OO4(ST7, s, st1, i, c); //0,2,8,12
printf("ST7 size: %ld\n", (long)sizeof(ST7)); //16
return 0;
}
系统默认对齐参数
#pragma pack能够改变编译器的默认对齐方式
#pragma pack(n) //设置编译器按照n个字节对齐,n可以取1,2,4,8,16
#pragma pack() //默认4字节对齐
#pragma pack(push) //将当前的对齐字节数压入栈顶,不改变对齐字节数
#pragma pack(push,n) //将当前的对齐字节数压入栈顶,并按照n字节对齐
#pragma pack(pop) //弹出栈顶对齐字节数,不改变对齐字节数
#pragma pack(pop,n) //弹出栈顶并直接丢弃,按照n字节对齐
#pragma pack(push,1) //可以指定结构的对齐和补齐的字节数 #pragma pack(pop) //恢复push前的值
#pragma pack()
应用示例
#define ASSERT_CONCAT_(a, b) a##b
#define STATIC_ASSERT(e, msg) ;enum { ASSERT_CONCAT_(assert_line_, __LINE__) = 1/(!!(e)) }
#pragma pack(push,1)
#if 0
RTP Header
0 1 2 3
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V=2|P|X| CC |M| PT | sequence number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| timestamp |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| synchronization source (SSRC) identifier |
+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
| contributing source (CSRC) identifier |
| ...defined by CC |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+---------------------------------------------------------------+
| payload |
| ... |
+---------------------------------------------------------------+
#endif
// rfc3550 rtp header should encode to network sequence
// The struct must be 12 bytes long, so force the compiler to pack it
// into a 12bytes long struct and not padding it.
typedef struct _RTPHeader_T {
# if LOCAL_LITTLE_ENDIAN /* little endian */
uint32_t CsrcCnt:4; /* 4bit */
uint32_t Extension:1; /* 1bit */
uint32_t Padding:1; /* 1bit */
uint32_t Version:2; /* 2bit */
uint32_t PayloadType:7; /* 7bit */
uint32_t Marker:1; /* 1bit */
# else /* big endian */
uint32_t Version:2; /* 2bit */
uint32_t Padding:1; /* 1bit */
uint32_t Extension:1; /* 1bit */
uint32_t CsrcCnt:4; /* 4bit */
uint32_t Marker:1; /* 1bit */
uint32_t PayloadType:7; /* 7bit */
# endif
uint32_t SeqNo:16; /* 16bit */
uint32_t TimeStamp; /* 32bit */
uint32_t Ssrc; /* 32bit */
} RTPHeader; //12bytes
STATIC_ASSERT(sizeof(RTPHeader)==12, "RTPHeader size doesn't seem to be cool.");
#pragma pack(pop)
// decode from network sequence
RTPHeader rtpHeader;
# if _RTP_CODEC_MANUAL
rtpHeader.Version = (((buff[0] & 0xFF) >> 6) & 0x03); /* 2bit */
rtpHeader.Padding = (((buff[0] & 0xFF) >> 5) & 0x01); /* 1bit */
rtpHeader.Extension = (((buff[0] & 0xFF) >> 4) & 0x01); /* 1bit */
rtpHeader.CsrcCnt = (buff[0] & 0x0F); /* 4bit */
rtpHeader.Marker = (((buff[1] & 0xFF) >> 7) & 0x01); /* 1bit */
rtpHeader.PayloadType = (buff[1] & 0x7F); /* 7bit */
rtpHeader.SeqNo = (buff[2] << 8) + buff[3]; /* 16bit */
rtpHeader.TimeStamp = (buff[4] << 24) + (buff[5] << 16) + (buff[6] << 8) + buff[7]; /* 32bit */
rtpHeader.Ssrc = (buff[8] << 24) + (buff[9] << 16) + (buff[10] << 8) + buff[11]; /* 32bit */
#else
RTPHeader *pHeader = (RTPHeader *)buff;
std::memcpy(&rtpHeader, buff, 12);
rtpHeader.SeqNo = ntohs(pHeader->SeqNo);
rtpHeader.TimeStamp = ntohl(pHeader->TimeStamp);
rtpHeader.Ssrc = ntohl(pHeader->Ssrc);
# endif
结构体大小分析
#include <stdio.h>
//假设存储位置都起始于0
#define OFFSET_OF(type, member) ((size_t)(&((type *)0)->member))
#define OO1(t, m1) #m1,OFFSET_OF(t, m1)
#define OO2(t, m1, m2) OO1(t, m1), OO1(t, m2)
#define OO3(t, m1, m2, m3) OO2(t, m1, m2), OO1(t, m3)
#define OO4(t, m1, m2, m3, m4) OO3(t, m1, m2, m3), OO1(t, m4)
#define OO5(t, m1, m2, m3, m4, m5) OO4(t, m1, m2, m3, m4), OO1(t, m5)
#define SHOW_OO1(t, m1) \
printf("\n%s offset: %s:%ld\n", #t,OO1(t,m1))
#define SHOW_OO2(t, m1, m2) \
printf("\n%s offset: %s:%ld, %s:%ld\n", #t,OO2(t,m1,m2))
#define SHOW_OO3(t, m1, m2, m3) \
printf("\n%s offset: %s:%ld, %s:%ld, %s:%ld\n", #t,OO3(t,m1,m2,m3))
#define SHOW_OO4(t, m1, m2, m3, m4) \
printf("\n%s offset: %s:%ld, %s:%ld, %s:%ld, %s:%ld\n", #t,OO4(t,m1,m2,m3,m4))
#define SHOW_OO5(t, m1, m2, m3, m4, m5) \
printf("\n%s offset: %s:%ld, %s:%ld, %s:%ld, %s:%ld, %s:%ld\n", #t,OO5(t,m1,m2,m3,m4,m5))
#pragma pack(8)
//#pragma pack(4)
//#pragma pack(2)
typedef struct S1 {
short a; // 2, a=min(pack, 2)
long b; // 8, a=min(pack, 8)
} S1;
typedef struct S2 {
char c; // 1, a=min(pack, 1)
S1 d; // 2+8,a=min(pack, max(2,8))
double e; // 8, a=min(pack,8)
} S2;
#pragma pack()
int main() {
printf("%ld, %ld\n", sizeof(S1), sizeof(S2));
SHOW_OO2(S1, a, b);
SHOW_OO3(S2, c, d, e);
return 0;
}
运行结果
#pragma pack(8)
16,32; 0,8; 0,8,24;#pragma pack(4)
12,24; 0,4; 0,4,16;#pragma pack(2)
10,20; 0,2; 0,2,12;