訂閱
糾錯(cuò)
加入自媒體

一文了解C語言中字節(jié)對齊的問題

最近一口君在做一個(gè)項(xiàng)目,遇到一個(gè)問題,運(yùn)行于ARM上的threadx在與DSP通信采用消息隊(duì)列的方式傳遞消息(最終實(shí)現(xiàn)原理是中斷+共享內(nèi)存的方式),在實(shí)際操作過程中發(fā)現(xiàn)threadx總是crash,于是經(jīng)過排查,是因?yàn)閭鬟f消息的結(jié)構(gòu)體沒有考慮字節(jié)對齊的問題。

隨手整理一下C語言中字節(jié)對齊的問題與大家一起分享。

一、概念

對齊跟數(shù)據(jù)在內(nèi)存中的位置有關(guān)。如果一個(gè)變量的內(nèi)存地址正好位于它長度的整數(shù)倍,他就被稱做自然對齊。比如在32位cpu下,假設(shè)一個(gè)整型變量的地址為0x00000004,那它就是自然對齊的。

首先了解什么位、字節(jié)、字

名稱英文名含義位bit1個(gè)二進(jìn)制位稱為1個(gè)bit字節(jié)Byte8個(gè)二進(jìn)制位稱為1個(gè)Byte字word電腦用來一次性處理事務(wù)的一個(gè)固定長度字長

一個(gè)字的位數(shù),現(xiàn)代電腦的字長通常為16,32, 64位。(一般N位系統(tǒng)的字長是N/8字節(jié)。)

不同的CPU一次可以處理的數(shù)據(jù)位數(shù)是不同的,32位CPU可以一次處理32位數(shù)據(jù),64位CPU可以一次處理64位數(shù)據(jù),這里的位,指的就是字長。

而所謂的字長,我們有時(shí)會稱為字(word)。在16位的CPU中,一個(gè)字剛好為兩個(gè)字節(jié),而32位CPU中,一個(gè)字是四個(gè)字節(jié)。若以字為單位,向上還有雙字(兩個(gè)字),四字(四個(gè)字)。

二、對齊規(guī)則

對于標(biāo)準(zhǔn)數(shù)據(jù)類型,它的地址只要是它的長度的整數(shù)倍就行了,而非標(biāo)準(zhǔn)數(shù)據(jù)類型按下面的原則對齊:  數(shù)組 :按照基本數(shù)據(jù)類型對齊,第一個(gè)對齊了后面的自然也就對齊了。聯(lián)合 :按其包含的長度最大的數(shù)據(jù)類型對齊。結(jié)構(gòu)體:結(jié)構(gòu)體中每個(gè)數(shù)據(jù)類型都要對齊。

三、如何限制定字節(jié)對齊位數(shù)? 1. 缺省

在缺省情況下,C編譯器為每一個(gè)變量或是數(shù)據(jù)單元按其自然對界條件分配空間。一般地,可以通過下面的方法來改變?nèi)笔〉膶鐥l件:

2. #pragma pack(n)

· 使用偽指令#pragma pack (n),C編譯器將按照n個(gè)字節(jié)對齊! 使用偽指令#pragma pack (),取消自定義字節(jié)對齊方式。

#pragma pack(n) 用來設(shè)定變量以n字節(jié)對齊方式。n字節(jié)對齊就是說變量存放的起始地址的偏移量有兩種情況:

如果n大于等于該變量所占用的字節(jié)數(shù),那么偏移量必須滿足默認(rèn)的對齊方式如果n小于該變量的類型所占用的字節(jié)數(shù),那么偏移量為n的倍數(shù),不用滿足默認(rèn)的對齊方式。

結(jié)構(gòu)的總大小也有一個(gè)約束條件,如果n大于等于所有成員變量類型所占用的字節(jié)數(shù),那么結(jié)構(gòu)的總大小必須為占用空間最大的變量占用的空間數(shù)的倍數(shù);否則必須是n的倍數(shù)。

3. __attribute

另外,還有如下的一種方式:· __attribute((aligned (n))),讓所作用的結(jié)構(gòu)成員對齊在n字節(jié)自然邊界上。如果結(jié)構(gòu)中有成員的長度大于n,則按照最大成員的長度來對齊! attribute ((packed)),取消結(jié)構(gòu)在編譯過程中的優(yōu)化對齊,按照實(shí)際占用字節(jié)數(shù)進(jìn)行對齊。

3. 匯編.a(chǎn)lign

匯編代碼通常用.a(chǎn)lign來制定字節(jié)對齊的位數(shù)。

.a(chǎn)lign:用來指定數(shù)據(jù)的對齊方式,格式如下:

.a(chǎn)lign [absexpr1, absexpr2]

以某種對齊方式,在未使用的存儲區(qū)域填充值. 第一個(gè)值表示對齊方式,4, 8,16或 32. 第二個(gè)表達(dá)式值表示填充的值。

四、為什么要對齊?

操作系統(tǒng)并非一個(gè)字節(jié)一個(gè)字節(jié)訪問內(nèi)存,而是按2,4,8這樣的字長來訪問。因此,當(dāng)CPU從存儲器讀數(shù)據(jù)到寄存器,IO的數(shù)據(jù)長度通常是字長。如32位系統(tǒng)訪問粒度是4字節(jié)(bytes), 64位系統(tǒng)的是8字節(jié)。當(dāng)被訪問的數(shù)據(jù)長度為n字節(jié)且該數(shù)據(jù)地址為n字節(jié)對齊時(shí),那么操作系統(tǒng)就可以高效地一次定位到數(shù)據(jù),無需多次讀取,處理對齊運(yùn)算等額外操作。數(shù)據(jù)結(jié)構(gòu)應(yīng)該盡可能地在自然邊界上對齊。如果訪問未對齊的內(nèi)存,CPU需要做兩次內(nèi)存訪問。

字節(jié)對齊可能帶來的隱患:

代碼中關(guān)于對齊的隱患,很多是隱式的。比如在強(qiáng)制類型轉(zhuǎn)換的時(shí)候。例如:

unsigned int i = 0x12345678;
unsigned char *p=NULL;
unsigned short *p1=NULL;
p=&i;
*p=0x00;
p1=(unsigned short *)(p+1);
*p1=0x0000;

最后兩句代碼,從奇數(shù)邊界去訪問unsignedshort型變量,顯然不符合對齊的規(guī)定。在x86上,類似的操作只會影響效率,但是在MIPS或者sparc上,可能就是一個(gè)error,因?yàn)樗鼈円蟊仨氉止?jié)對齊.

五、舉例 例1:os基本數(shù)據(jù)類型占用的字節(jié)數(shù)

首先查看操作系統(tǒng)的位數(shù)

在64位操作系統(tǒng)下查看基本數(shù)據(jù)類型占用的字節(jié)數(shù):

#include

例2:結(jié)構(gòu)體占用的內(nèi)存大小--默認(rèn)規(guī)則

考慮下面的結(jié)構(gòu)體占用的位數(shù)

struct yikou_s
{
   double d;
   char c;
   int i;
} yikou_t;

執(zhí)行結(jié)果

sizeof(yikou_t) = 16

在內(nèi)容中各變量位置關(guān)系如下:

其中成員C的位置還受字節(jié)序的影響,有的可能在位置8

編譯器給我們進(jìn)行了內(nèi)存對齊,各成員變量存放的起始地址相對于結(jié)構(gòu)的起始地址的偏移量必須為該變量類型所占用的字節(jié)數(shù)的倍數(shù), 且結(jié)構(gòu)的大小為該結(jié)構(gòu)中占用最大空間的類型所占用的字節(jié)數(shù)的倍數(shù)。

對于偏移量:變量type n起始地址相對于結(jié)構(gòu)體起始地址的偏移量必須為sizeof(type(n))的倍數(shù)結(jié)構(gòu)體大小:必須為成員最大類型字節(jié)的倍數(shù)

char: 偏移量必須為sizeof(char) 即1的倍數(shù)
int: 偏移量必須為sizeof(int) 即4的倍數(shù)
float: 偏移量必須為sizeof(float) 即4的倍數(shù)
double: 偏移量必須為sizeof(double) 即8的倍數(shù)
例3:調(diào)整結(jié)構(gòu)體大小

我們將結(jié)構(gòu)體中變量的位置做以下調(diào)整:

struct yikou_s
{
   char c;
   double d;
   int i;
} yikou_t;

執(zhí)行結(jié)果

sizeof(yikou_t) = 24

各變量在內(nèi)存中布局如下:

當(dāng)結(jié)構(gòu)體中有嵌套符合成員時(shí),復(fù)合成員相對于結(jié)構(gòu)體首地址偏移量是復(fù)合成員最寬基本類型大小的整數(shù)倍。

例4:#pragma pack(4)#pragma pack(4)
struct yikou_s
{
   char c;
   double d;
   int i;
} yikou_t;
sizeof(yikou_t) = 16
例5:#pragma pack(8)#pragma pack(8)
struct yikou_s
{
   char c;
   double d;
   int i;
} yikou_t;
sizeof(yikou_t) = 24
例6:匯編代碼

舉例:以下是截取的uboot代碼中異常向量irq、fiq的入口位置代碼:

六、匯總實(shí)力

有手懶的同學(xué),直接貼一個(gè)完整的例子給你們:

#include

聲明: 本文由入駐維科號的作者撰寫,觀點(diǎn)僅代表作者本人,不代表OFweek立場。如有侵權(quán)或其他問題,請聯(lián)系舉報(bào)。

發(fā)表評論

0條評論,0人參與

請輸入評論內(nèi)容...

請輸入評論/評論長度6~500個(gè)字

您提交的評論過于頻繁,請輸入驗(yàn)證碼繼續(xù)

  • 看不清,點(diǎn)擊換一張  刷新

暫無評論

暫無評論

人工智能 獵頭職位 更多
掃碼關(guān)注公眾號
OFweek人工智能網(wǎng)
獲取更多精彩內(nèi)容
文章糾錯(cuò)
x
*文字標(biāo)題:
*糾錯(cuò)內(nèi)容:
聯(lián)系郵箱:
*驗(yàn) 證 碼:

粵公網(wǎng)安備 44030502002758號