问题现象
在调试一个软件功能时,发现一个结构体对齐的问题,以前没有太关注,现在把它总结出来。先看示例:
- 结构体1:
typedef struct
{
char magic[4];
uint32_t crc32;
uint32_t lenght;
uint16_t ver;
uint16_t IFrameCnt;
uint32_t startTime;
uint32_t startPts;
uint32_t sliceTimeLen;
uint32_t sliceLen;
uint8_t res[16];
}RecSliceIndexHeader_t;
RecSliceIndexHeader_t的大小为:sizeof(RecSliceIndexHeader_t) = 48
- 结构体2:
typedef struct
{
uint8_t codecType;
uint8_t frameRate;
uint8_t colorDepth;
uint8_t frameInterval;
int32_t quality;
uint32_t bitRate;
uint16_t width;
uint16_t height;
uint8_t isEncrypt;
uint8_t res[15];
}RecSliceVideoParam_t;
通过sizeof得到结构体RecSliceVideoParam_t的大小为:sizeof(RecSliceVideoParam_t) = 32;
- 结构体3:
typedef struct
{
uint8_t codecType;
uint8_t channel;
uint32_t sampleRate;
uint16_t bitsPerSample;
uint8_t res[16];
}RecSliceAudioParam_t;
通过sizeof得到结构体RecSliceAudioParam_t的大小为:sizeof(RecSliceAudioParam_t) = 28;
- 结构体4:
typedef struct
{
uint64_t time;
uint32_t offset;
uint32_t IFrameLen;
uint8_t res[16];
}RecIFrameInfo_t;
通过sizeof得到结构体RecIFrameInfo_t的大小为:sizeof(RecIFrameInfo_t) = 32;
- 结构体5:
typedef struct
{
RecSliceIndexHeader_t head;
RecSliceVideoParam_t video;
RecSliceAudioParam_t audio;
RecIFrameInfo_t iFrame[1];
}RecSliceIndexInfo_t;
通过sizeof得到结构体RecSliceIndexInfo_t的大小为:sizeof(RecSliceIndexInfo_t) = 144;
((unsigned long)(&((RecSliceIndexInfo_t*)0)->IFrameInfo[0])) = 112;
sizeof(RecSliceAudioParam_t) +sizeof(RecSliceVideoParam_t)+sizeof(RecSliceIndexHeader_t) = 108
问题分析
问题1
关于结构体3,它在内存中的实际对齐方式是:
typedef struct
{
uint8_t codecType;
uint8_t channel;
uint32_t sampleRate;
uint16_t bitsPerSample;
uint8_t res[16];
}RecSliceAudioParam_t;
它实际的对齐方式如下:
typedef struct
{
uint8_t codecType;
uint8_t channel;
uint8_t res1[2]; // 编译器加对齐的字节
uint32_t sampleRate;
uint16_t bitsPerSample;
uint8_t res1[2]; // 编译器加对齐的字节
uint8_t res[16];
}RecSliceAudioParam_t;
所以最终输出的值为:sizeof(RecSliceAudioParam_t) = 28;这个问题的关键是君正的字节对齐方式问题,在结构体内,它只要碰到4个字节的变量就会将结构体做4字节对齐。
问题2:
关于结构体5,sizeof(RecSliceAudioParam_t)+sizeof(RecSliceVideoParam_t)+sizeof(RecSliceIndexHeader_t) 的值为108,我们发现RecIFrameInfo_t的成员uint64_t time的大小为8字节,因此导致108无法被8整除,在RecSliceIndexInfo_t 的IFrameInfo元素会填充4字节。因此成员IFrameInfo前面的元素整个大小为112字节。这个问题在组合结构体尤其需要关注。
改进措施
在没有#pragma pack这个宏的声明下,结构体的对齐遵循下面四个原则:
- 第一个成员的首地址为0。
- 结构体的起始地址能够被其最宽的成员大小整除;
- 结构体每个成员相对于起始地址的偏移能够被其自身大小整除,如果不能则在前一个成员后面补充字节。
- 结构体总体大小能够被最宽的成员的大小整除,如不能则在后面补充字节。
一般来说,不太建议使用#pragma pack这个宏,从找的资料看看,使用这个宏强制对齐在不同的平台可能会导致异常的BUG或者死机。
一般来说定义结构体遵循如下步骤,定义芯片平台对齐的字节数为PPB(以4字节对齐芯片平台举例,PPB=4):
- 在单个结构体中,前面定义的成员遇到PPB大小的成员时,需要手动添加保留直接做对齐,或者调整成员的顺序,下面举例说明:
比如定义一个结构体:
里面有x1、x2、x3、x4、x5、x6共6个元素,其中x1、x2、x3为char,x4为int类型即大小为4,和PPB相同。那么结构体定义的时候就要确保x1+x2+x3的大小是x4的整数倍(即4的整数倍),即需要在x1、x2、x3和x3之后再增加一个字节保证在x4之前对齐;(总的来说对应的是结构体的对齐遵循下面四个原则的第三点); - 在复合结构体中(结构体中包含结构体)
上面问题遇到的问题,主要是复合结构遇到的问题,RecSliceIndexInfo_t的IFrameInfo是RecIFrameInfo_t结构体,这个RecIFrameInfo_t的time是8字节的,前面的所有长度为108,因此不够8整除,需要在前面三个成员加上4字节做对齐,即112字节。所以复合结构体中某个结构体有大于PPB的成员,需要仔细检查整个结构体的对齐问题。