嵌入式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語言之——結構體對齊詳解


分享到:


相關文章: