C语言字节对齐

来源:互联网 发布:疾病自测软件 编辑:程序博客网 时间:2024/04/30 20:47

主机环境:Windows 7 SP1 64位

开发环境:Code::Blocks16.01


这几天在看一个JPEG编码的库代码时看到了一个关于字节对齐的预处理指令#pragma pack(n),之前也偶遇过这类指令,但也没去研究过,现在就整理一下有关字节对齐的问题,以便日后查阅,跟这个有关的指令还有一类:__attribute__((aligned(n))),下面就分别分析一下这两类指令,对于一个结构体而言,大多数情况下我们并不关心其里面的成员是如何存储的,只要值是对的即可,但某些情况下我们却又需要知道其存储方式,而且还想要它按照我们预定的方式来存储。之所以要用到字节对齐方式一部分是因为CPU是按照其字长来访问数据的,比如说STM32,其以32位方式访问数据,如果我们存储的变量不在4字节的整数倍时,CPU就需要两次访问才能得到我们所需要的指令,对于一些时序要求严格的情况而言,这显然不是我们所想要的结果;另外一部分是我们读取一串数据流,且这一段数据流是有格式定义的,我们需要把这一段数据流填充至一个结构体中(当然结构体的成员变量其自然长度不一致),这也需要用到字节对齐。

首先看下#pragma pack(n)指令,

敲一段代码:

#include <stdio.h>#include <stdlib.h>#include <stdint.h>typedef struct{    uint16_t a;    uint8_t b;    uint32_t c;    uint8_t d;}STR_Test;int main(int argc, char *args[]){    STR_Test strTest;    printf("StrTest_addr:%X\n",(uint32_t)&strTest);    printf("sizeof(StrTest):%d\n",sizeof(strTest));    printf("a_addr:%X\n",(uint32_t)&strTest.a - (uint32_t)&strTest);    printf("b_addr:%X\n",(uint32_t)&strTest.b - (uint32_t)&strTest);    printf("c_addr:%X\n",(uint32_t)&strTest.c - (uint32_t)&strTest);    printf("d_addr:%X\n",(uint32_t)&strTest.d - (uint32_t)&strTest);}
其运行结果如下:


可以看到,在默认情况下,结构体按照成员自然长度对齐,a占两个字节,b占两个字节(一个字节有效,一个字节保留),c占4个字节,d占4个字节,共12个字节。默认情况下结构体大小跟它里面成员自然长度最大的有关,这里面c的长度最大为4字节,因此这里,a和b占用4个字节,c独自占用4个字节,d独自占用4个字节。现在我们来使用预处理指令修饰一下STR_Test结构体的定义,

如下:

#pragma pack(4)typedef struct{    uint16_t a;    uint8_t b;    uint32_t c;    uint8_t d;}STR_Test;#pragma pack()
再次运行,结果跟之前的一模一样,#pragma pack(n)的含义是设置变量以n字节对齐,#pragma pack()的含义是取消对变量的对齐限制,使用默认的设置,同时要注意,这里的n是设置的最小值,其要跟结构体成员中各个自然长度进行比较,如果n大于成员的自然长度则按成员自然长度对齐,而如果n小于成员自然长度,则按n字节对齐,大家可以去MSDN上查看其说明,如下:


后面依次设置n为8,16,运行的结果跟之前的依然一模一样,即对STR_Test而言n>=4就不再有影响了,都是按成员自然长度对齐。现在设置n=2,查看结果,如下:


这里有两个值出现了改动,一个是StrTest_addr值变为了0X28FF16,另一个是StrTest的大小变为了10,StrTest大小变为10很容易理解,即d所占用的空间变为了2个字节。而地址的区别是0X28FF16不可以被4整除,而0X28FF14可以被4整除,即#pragma packt(n)会影响结构体的地址,一般而言其地址是n的整数倍,这样便于CPU对该结构体变量的寻址,由于前面的n>=4的效果相同,所以按4的整数倍来处理,至于n=2时地址变为了0X28FF16而不是0x28FF12等我想可能跟系统有关吧。再来看下n=1时的结果:


可以看到结构体大小变为了8,即结构体成员之间没有保留字节,这个比较常用,因为我们获取一串数据流一般是按字节获取的,如果用结构体来存储这段数据流则其成员之间就不能有保留字节,一定得紧密连接。

接着来看一下嵌入式系统中常用的__attribute__特性,

__attribute__有三种功能:它可以用来修饰函数、类型、变量,__attribute__是GNU编译器的特性,__attribute__有很多参数,而且其作用域是在";"之前,这里只说一下__attribute__ ((aligned(n)))其用来指定某一类型对齐的最小字节数。

修改代码如下:

typedef struct{    uint16_t a;    uint8_t b;    uint32_t c;    uint8_t d;}__attribute__ ((aligned(1)))STR_Test;
分别设置__attribute__ ((aligned(m)))中m为1,2,4,8,16,32等查看效果

m=1,2,4时:


m=8,16时:


m=32时:


对比可知strTest中成员相对地址没有变化,变化的只是strTest的大小以及地址,m指定的只是对齐的最小字节数,只有当m大于结构体中成员自然长度的最大值时其才起作用,对strTest而言即m>4,同时可以看到0x28FF10可以被8和16整除,而0x28FF00可以被32整除,__attribute__ ((aligned(m)))特性并不修改结构体中成员的存储结构,只是修改其存储空间及地址,以便于CPU访问,而#pragma pack(n)是会改变结构体成员的存储结构及地址,二者都有限制值,n大于等于结构体中成员自然长度最大值时就不再起作用,而m小于等于结构体中成员自然长度最大值时不再起作用。

关于__attribute__特性还有一个常用的是__attribute__ ((packed))--取消对结构体的对齐的设置,按照自然长度对齐。

先到这吧,以后有遇到新的知识点再来补充吧。




0 0