嵌入式C语言之——结构体对齐详解

引言

结构体作为日常开发中使用最广泛的数据类型之一,其重要性不言而喻 。上一篇我们介绍了结构体的定义, 变量的初始化, 以及结构体元素的访问方式。 今天我们再更深入的研究下结构体对齐的相关问题, 这也是很多小伙伴在编写代码中常常忽视的细节,从而导致各种各样的bug, 同时也是很多面试和笔试中经常遇见的问题之一。let's go~

结构体的大小

这里我们定义一个结构体,如下:

<code> 

struct

test

{

char

a;

int

c; };

struct

test

t1

;

/<code>

看到这里,我想请问下大家,结构体变量t1所占内存空间大小是多少呢? 是1+ 4 = 5字节嘛?

我们来测试下:

<code>

int

main

(

void

)

{

printf

(

"sizeof(t1) = %d.\n"

,

sizeof

(struct test));

return

0

; }/<code>

编译运行:

嵌入式C语言之——结构体对齐详解

结果居然是8!是否和大家想的一样呢? 为什么会这样呢, 明明这个结构体中元素a(char类型, 占1个字节) 和 元素b(int类型,占4个字节), 而最终结构体的大小却是8呢?

结构体对齐

这是因为结构体要考虑元素的对齐访问,所以导致每个元素实际占的字节数和字节本身的类型所占的字节数不一定完全一致。 好比如上面的例子, 其实就是4字节对齐访问(当然以多少字节对齐,是可以自己手动设置的,文章后面有详细介绍), 先上图,感受下:

嵌入式C语言之——结构体对齐详解

看图理解: 首先元素a占用一个字节(一个格子,绿色部分),而这一排的其余三个格子(地址) 因为装不下元素b(占4个字节,即格子), 因此这三个格子被空闲(但其空间是属于结构体的本身的),接着从0x8地址开始装元素b(4个格子,即红色部分), 因此是该结构体变量t1最终占用8字节大小的内存空间。

趁热打铁: 来个练习题,加深下理解:

<code> 

 

struct

test1

{

char

a; short b; short c;

char

d[

3

];

int

e; };

int

main

(

void

)

{

struct

test1

t2

;

printf

(

"sizeof(t2) = %d.\n"

,

sizeof

(struct test1));

return

0

; }/<code>

分析:首排盒子(4个小格子)先装下元素a(1个格子),还剩余3个格子,可以装下元素b(占2个格子), 剩余的一个格子已经装不下元素c(占2个格子)了, 因此启用新的大盒子,首先来装下元素c(2个格子), 然后剩余2个格子,我们可以再装元素d[0], d[1],接着再启用新的大盒子来装元素d[2], 此时剩余3个格子,不能装下元素e(4个格子), 因此又得启用新的大盒子,刚好完整装下。 最终盒子,我们共启用了4个大盒子,即4*4(小格子) = 16字节, 上图:

嵌入式C语言之——结构体对齐详解

编译运行:

嵌入式C语言之——结构体对齐详解

设置对齐指令

以多少字节对齐是由编译器来管理的,因此我们可以通过一些编译器指令来进行设置。这里我们以gcc编译器为例:

第一种:

一般C语言编译都支持该指令,如gcc ,ARMCC编译器:

<code> 
 
 
/<code>

这种写法是以#prgama pack(n)开头, 并以#prgama pack()结尾,在此区间的代码都是按照n为对齐访问。通过例子来感受下:

<code> 

 
 

struct

test

{

char

a;

int

c; };

int

main

(

void

)

{

struct

test

t1

;

printf

(

"sizeof(t1) = %d.\n"

,

sizeof

(struct test));

return

0

; } /<code>

上面的例子在文章开头时运行的结果是8,现在通过#pragma pack(1) 设置成1字节对齐访问,来看看运行结果:

嵌入式C语言之——结构体对齐详解

结果是5,与我们默认4字节对齐运行结果(8)不一样哦,现在大家应该能明白其中的缘由了吧。

第二种:

其实也是现在使用更多,更被推荐的对齐指令(针对特定的数据类型):

<code> 

__attribute__

((aligned(n)))

__attribute__

((packed)) /<code>

注意: __attribute__((packed))使用时直接放在要进行内存对齐的类型定义的后面,然后它起作用的范围只有加了这个东西的这一个类型。packed的作用就是取消对齐访问。通过例子感受下:

<code> 

 
 __attribute__((aligned(

1

)))

struct

test1

{

char

a; short b; short c;

char

d[

3

];

int

e; }__attribute__((packed));

int

main

(

void

)

{

struct

test1

t2

;

printf

(

"sizeof(t2) = %d.\n"

,

sizeof

(struct test1));

return

0

; }/<code>

编译运行结果:

嵌入式C语言之——结构体对齐详解

我们通过__attribute__((aligned(1)))设置1字节对齐,即结构体变量所占内存空间: a(1字节) + b(2字节) + c(2字节) + d(三个元素,3字节) + e(4字节) = 12字节,完全一致。

思考和总结

为什么要设置对齐访问,到底哪些情况需要设置和应该设置成多少字节对齐呢?

其实对齐访问主要原因是为了配合硬件,也就是说硬件本身有物理上的限制,如果对齐排布和访问会提高效率,否则会大大降低效率。例如在lcd驱动代码中,常常会将相关缓存数据会设置512或更多字节对齐,以达到更好的刷新频率。 总结来说是以空间换取性能,使硬件(如MMC, Cache等硬件)达到更高的速度和效率。

嵌入式C语言之——结构体对齐详解


分享到:


相關文章: