最近作者在做一個項目,遇到一個問題,運行于ARM上的threadx在與DSP通信采用消息隊列的方式傳遞消息(最終實現(xiàn)原理是中斷+共享內(nèi)存的方式),在實際操作過程中發(fā)現(xiàn)threadx總是crash,于是經(jīng)過排查,是因為傳遞消息的結(jié)構(gòu)體沒有考慮字節(jié)對齊的問題。
隨手整理一下C語言中字節(jié)對齊的問題與大家一起分享。
一、概念
對齊跟數(shù)據(jù)在內(nèi)存中的位置有關(guān)。如果一個變量的內(nèi)存地址正好位于它長度的整數(shù)倍,他就被稱做自然對齊。比如在32位cpu下,假設(shè)一個整型變量的地址為0x00000004,那它就是自然對齊的。
首先了解什么位、字節(jié)、字
位 | bit | 1個二進制位稱為1個bit |
字節(jié) | Byte | 8個二進制位稱為1個Byte |
字 | word | 電腦用來一次性處理事務(wù)的一個固定長度 |
名稱 | 英文名 | 含義 |
---|
字長
一個字的位數(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ù),這里的位,指的就是字長。
而所謂的字長,我們有時會稱為字(word)。在16位的CPU中,一個字剛好為兩個字節(jié),而32位CPU中,一個字是四個字節(jié)。若以字為單位,向上還有雙字(兩個字),四字(四個字)。
二、對齊規(guī)則
對于標準數(shù)據(jù)類型,它的地址只要是它的長度的整數(shù)倍就行了,而非標準數(shù)據(jù)類型按下面的原則對齊:數(shù)組 :按照基本數(shù)據(jù)類型對齊,第一個對齊了后面的自然也就對齊了。聯(lián)合 :按其包含的長度最大的數(shù)據(jù)類型對齊。結(jié)構(gòu)體:結(jié)構(gòu)體中每個數(shù)據(jù)類型都要對齊。
三、如何限制定字節(jié)對齊位數(shù)?
1. 缺省
在缺省情況下,C編譯器為每一個變量或是數(shù)據(jù)單元按其自然對界條件分配空間。一般地,可以通過下面的方法來改變?nèi)笔〉膶鐥l件:
2. #pragma pack(n)
· 使用偽指令#pragma pack (n),C編譯器將按照n個字節(jié)對齊?!?使用偽指令#pragma pack (),取消自定義字節(jié)對齊方式。
#pragma pack(n) 用來設(shè)定變量以n字節(jié)對齊方式。n字節(jié)對齊就是說變量存放的起始地址的偏移量有兩種情況:
如果n大于等于該變量所占用的字節(jié)數(shù),那么偏移量必須滿足默認的對齊方式
如果n小于該變量的類型所占用的字節(jié)數(shù),那么偏移量為n的倍數(shù),不用滿足默認的對齊方式。
結(jié)構(gòu)的總大小也有一個約束條件,如果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)化對齊,按照實際占用字節(jié)數(shù)進行對齊。
3. 匯編.align
匯編代碼通常用.align來制定字節(jié)對齊的位數(shù)。
.align:用來指定數(shù)據(jù)的對齊方式,格式如下:
.align[absexpr1,absexpr2]
以某種對齊方式,在未使用的存儲區(qū)域填充值. 第一個值表示對齊方式,4, 8,16或 32. 第二個表達式值表示填充的值。
四、為什么要對齊?
操作系統(tǒng)并非一個字節(jié)一個字節(jié)訪問內(nèi)存,而是按2,4,8這樣的字長來訪問。因此,當CPU從存儲器讀數(shù)據(jù)到寄存器,IO的數(shù)據(jù)長度通常是字長。如32位系統(tǒng)訪問粒度是4字節(jié)(bytes), 64位系統(tǒng)的是8字節(jié)。當被訪問的數(shù)據(jù)長度為n字節(jié)且該數(shù)據(jù)地址為n字節(jié)對齊時,那么操作系統(tǒng)就可以高效地一次定位到數(shù)據(jù),無需多次讀取,處理對齊運算等額外操作。數(shù)據(jù)結(jié)構(gòu)應(yīng)該盡可能地在自然邊界上對齊。如果訪問未對齊的內(nèi)存,CPU需要做兩次內(nèi)存訪問。
字節(jié)對齊可能帶來的隱患:
代碼中關(guān)于對齊的隱患,很多是隱式的。比如在強制類型轉(zhuǎn)換的時候。例如:
unsignedinti=0x12345678; unsignedchar*p=NULL; unsignedshort*p1=NULL; p=&i; *p=0x00; p1=(unsignedshort*)(p+1); *p1=0x0000;
最后兩句代碼,從奇數(shù)邊界去訪問unsignedshort型變量,顯然不符合對齊的規(guī)定。在x86上,類似的操作只會影響效率,但是在MIPS或者sparc上,可能就是一個error,因為它們要求必須字節(jié)對齊.
五、舉例
例1:os基本數(shù)據(jù)類型占用的字節(jié)數(shù)
首先查看操作系統(tǒng)的位數(shù)
在64位操作系統(tǒng)下查看基本數(shù)據(jù)類型占用的字節(jié)數(shù):
#include
例2:結(jié)構(gòu)體占用的內(nèi)存大小--默認規(guī)則
考慮下面的結(jié)構(gòu)體占用的位數(shù)
structyikou_s { doubled; charc; inti; }yikou_t;
執(zhí)行結(jié)果
sizeof(yikou_t)=16
在內(nèi)容中各變量位置關(guān)系如下:
其中成員C的位置還受字節(jié)序的影響,有的可能在位置8
編譯器給我們進行了內(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)體大?。罕仨殲槌蓡T最大類型字節(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)整:
structyikou_s { charc; doubled; inti; }yikou_t;
執(zhí)行結(jié)果
sizeof(yikou_t)=24
各變量在內(nèi)存中布局如下:
當結(jié)構(gòu)體中有嵌套符合成員時,復(fù)合成員相對于結(jié)構(gòu)體首地址偏移量是復(fù)合成員最寬基本類型大小的整數(shù)倍。
例4:#pragma pack(4)
#pragmapack(4) structyikou_s { charc; doubled; inti; }yikou_t;sizeof(yikou_t)=16
例5:#pragma pack(8)
#pragmapack(8) structyikou_s { charc; doubled; inti; }yikou_t;sizeof(yikou_t)=24
例6:匯編代碼
舉例:以下是截取的uboot代碼中異常向量irq、fiq的入口位置代碼:
六、匯總實力
有手懶的同學(xué),直接貼一個完整的例子給你們:
#include
責任編輯:haq
-
Linux
+關(guān)注
關(guān)注
87文章
11350瀏覽量
210466 -
C語言
+關(guān)注
關(guān)注
180文章
7614瀏覽量
137780 -
字節(jié)
+關(guān)注
關(guān)注
0文章
41瀏覽量
13815
原文標題:Linux字節(jié)對齊的那些事
文章出處:【微信號:A1411464185,微信公眾號:multisim】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
EE-62:在C語言中訪問短字內(nèi)存
![EE-62:在<b class='flag-5'>C</b><b class='flag-5'>語言中</b>訪問短字內(nèi)存](https://file.elecfans.com/web1/M00/D9/4E/pIYBAF_1ac2Ac0EEAABDkS1IP1s689.png)
EE-128:C語言中的DSP:從C調(diào)用匯編類成員函數(shù)
![EE-128:<b class='flag-5'>C</b><b class='flag-5'>語言中</b>的DSP:從<b class='flag-5'>C</b>調(diào)用匯編類成員函數(shù)](https://file.elecfans.com/web1/M00/D9/4E/pIYBAF_1ac2Ac0EEAABDkS1IP1s689.png)
C語言中申請的堆內(nèi)存能不能自動釋放
C語言中的socket編程基礎(chǔ)
c語言中從左到右結(jié)合怎么看
求助,關(guān)于STM32H7的Cache無效化操作32字節(jié)對齊問題求解
C語言基礎(chǔ)-為什么要使用C?
嵌入式系統(tǒng)中C語言結(jié)構(gòu)體的基礎(chǔ)實現(xiàn)與應(yīng)用
![嵌入式系統(tǒng)中<b class='flag-5'>C</b><b class='flag-5'>語言</b>結(jié)構(gòu)體的基礎(chǔ)實現(xiàn)與應(yīng)用](https://file1.elecfans.com/web2/M00/C4/E6/wKgaomXv9r6Aaj6DAAAYJ3PfWAU110.jpg)
C語言中的typedef的應(yīng)用
![<b class='flag-5'>C</b><b class='flag-5'>語言中</b>的typedef的應(yīng)用](https://file1.elecfans.com/web2/M00/C3/B9/wKgaomXn5LGAbFesAAAe4pvOxcc159.png)
C語言#define的應(yīng)用
![<b class='flag-5'>C</b><b class='flag-5'>語言</b>#define的應(yīng)用](https://file1.elecfans.com/web2/M00/C3/B9/wKgaomXn42eACLumAAAag1PydyU214.png)
C語言中的位域典型的實例
![<b class='flag-5'>C</b><b class='flag-5'>語言中</b>的位域典型的實例](https://file1.elecfans.com/web2/M00/C3/6C/wKgaomXlQG6AFgLTAAAJPiv6Ebk706.jpg)
介紹C語言中錯誤處理和異常處理的一些常用的方法和策略
C語言中的可變參數(shù)介紹
![<b class='flag-5'>C</b><b class='flag-5'>語言中</b>的可變參數(shù)介紹](https://file1.elecfans.com/web2/M00/C2/BA/wKgaomXezG6AcBLhAABI4KQSKz0893.png)
評論