這個內容也是很重要的乙個,所以,這裡對一些問題和規律做乙個總結。
涉及到的幾個概念:
記憶體對齊:
現代計算機中記憶體空間都是按照byte劃分的,從理論上講似乎對任何型別的變數的訪問可以從任何位址開始,但實際情況是在訪問特定變數的時候經常在特定的記憶體位址訪問,這就需要各型別資料按照一定的規則在空間上排列,而不是順序的乙個接乙個的排放,這就是
對齊。
對齊的作用和原因:
各個硬體平台對儲存空間的處理上有很大的不同。一些平台對某些特定型別的資料只能從某些特定位址開始訪問。其他平台可能沒有這種情況,但是最常見的是如果不按照適合其平台要求對資料存放進行對齊,會在訪問效率上帶來損失。比如有些平台每次讀都是從偶位址開始,如果乙個int型(假設為32位系統)如果存放在偶位址開始的地方,那麼乙個讀週期就可以讀出,而如果存放在奇位址開始的地方,就可能會需要2個讀週期,並對兩次讀出的結果的高低位元組進行拼湊才能得到該int資料。顯然在讀取效率上下降很多。
對齊的實現:
通常,我們寫程式的時候,不需要考慮對齊問題。編譯器會替我們選擇適合目標平台的對齊策略。當然,我們也可以通知給編譯器傳遞預編譯指令而改變對指定資料的對齊方法。
但是,正因為我們一般不需要關心這個問題,所以因為編輯器對資料存放做了對齊,而我們不了解的話,常常會對一些問題感到迷惑。最常見的就是struct資料結構的sizeof結果,出乎意料。為此,我們需要對對齊演算法所了解。
對齊的演算法
由於各個平台和編譯器的不同,本人使用的gcc version 4.3.2編譯器(32位x86平台)為例子,來討論編譯器對struct資料結構中的各成員如何進行對齊的。這個平台是偶位址開始讀取的。
假設結構體定義如下:
struct sa
;
結構體sa中包含了乙個4byte長度的int,乙個1byte長度的char,乙個2byte長度的short。那麼sa用到的空間應該是7byte,但是,sizeof(sa)結果卻是8。這是因為編譯器在對資料成員在空間上進行對齊了。
下面調整一下sa的成員順序:
struct sb
;
按照我們之前的分析,sb的空間大小也應該是7byte,但是,sizeof(sb)的結果卻為12。
只是順序稍微進行了調整,但是結果卻相差很大,這是為什麼呢? 因為編譯器做了手腳。
接下來,我們利用
預編譯指令#pragma pack (value)來讓編譯器使用我們自己指定的對齊值取代預設值,按照我們自己的想法來做事。
#pragma pack (1) /* 指定按1byte對齊 */
struct sc
;#pragma pack () /* 取消指定對齊,恢復預設對齊 */
接下來,我們利用
預編譯指令#pragma pack (value)來讓編譯器使用我們自己指定的對齊值取代預設值,按照我們自己的想法來做事。
此時sizeof(sc)的結果為:7。
再進行一次測試:
#pragma pack (2) /* 指定按2byte對齊 */
struct sd
;#pragma pack () /* 取消指定對齊,恢復預設對齊 */
此時sizeof(sd)的結果為:8。
其實,這裡有幾個概念值:
1.資料型別自身的對齊值:基本資料型別的自身對齊值:
對於char型資料,其自身對齊值為1,對於short型為2,對於int,float型別,其自身對齊值為4單位位元組,double型為8。
2.指定對齊值:#progma pack (value)時的指定對齊值value。
3.結構體或者類的自身對齊值:其成員中自身對齊值最大的那個值。
4.資料成員、結構體和類的有效對齊值:自身對齊值和指定對齊值中小的那個值。
其中,有效對齊值n
是最終用來決定資料存放位址方式的值,最重要。
(1)有效對齊n,就是表示「對齊在n上」,也就是說該資料的"存放起始位址%n=0".
(2)而資料結構中的資料變數都是按定義的先後順序來排放的。第乙個資料變數的起始位址就是資料結構的起始位址。
(3)結構體的成員變數要對齊排放,結構體本身也要根據自身的有效對齊值圓整——就是結構體成員變數占用總長度需要是對結構體有效對齊值的整數倍。
這樣就不能理解上面的幾個例子的值了。
例子分析:
對於結構體sb
struct sb
;
假設sb從位址0x0000處開始排放,此時沒有指定對齊值,本人的是預設是4。
第乙個成員變數b,其自身的對齊值為1,指定或缺省的為4,取較小者,則b的有效對齊值為1。
所以其存放位址0x0000符合 0x0000%1=0;
第二個成員變數a,其自身的對齊值為4,
指定或缺省的為4,則a的有效對齊值為4,所以a只能存放在0x0004-0x0007這連續的4個位元組空間中,符合
0x0004%4=0
且緊靠第乙個變數。
第三個成員變數c,其自身的對齊值為2,所以c的有效對齊值為2。可以存放在0x0008-0x0009這兩個連續的位元組空間中,符合0x0008%2=0;
所以,0x0000-0x0009存放的是結構體sb的內容。
再看資料結構sb自身對齊值(
其成員中自身對齊值最大的那個值)
這裡是a,所以sb自身的有效對齊值為4。根據結構體圓整,由於0x0000-0x0009有10個位元組,而(10+2)%4=0,所以0x000a-0x000b也被結構體sb占用。
故b從0x0000到0x000b共有12個位元組,sizeof(sb)=12;
同理,對於結構體sc:
#pragma pack (1) /* 指定按1byte對齊 */
struct sc
;#pragma pack () /* 取消指定對齊,恢復預設對齊 */
由於我們自己指定了有效值,所以,每個的有效值都是1。假設sc從0x0000開始存放,那麼,變數b、a、c的位址依次為:0x0000、0x0001-0x0004、0x0005-0x0006. 總共有7byte。
sizeof(sc)=7.
對於結構體sd:
#pragma pack (2) /* 指定按2byte對齊 */
struct sd
;#pragma pack () /* 取消指定對齊,恢復預設對齊 */
假設sd從位址0x0000處開始排放,此時自己指定對齊值是4。
第乙個成員變數b,其自身的對齊值為1,指定的對齊值為2,取較小者,則b的有效對齊值為1。
所以其存放位址0x0000符合 0x0000%1=0;
第二個成員變數a,其自身的對齊值為4,
指定的對齊值為2,則a的有效對齊值為2,所以a只能存放在0x0002-0x0005這連續的4個位元組空間中,符合
0x0002%2=0
且緊靠第乙個變數。
第三個成員變數c,其自身的對齊值為2,
指定的對齊值為2,
所以c的有效對齊值為2。可以存放在0x0006-0x0007這兩個連續的位元組空間中,符合0x0006%2=0;
所以,0x0000-0x0007存放的是結構體sb的內容。
再看資料結構sb自身對齊值(
其成員中自身對齊值最大的那個值)
這裡是a,所以sb自身的有效對齊值為4。根據結構體圓整,由於0x0000-0x0007有8個位元組,而結構體sd自身的對齊值為4,
所以sc的有效對齊值為2,
而8%2=0。
故b從0x0000到0x0007共有8個位元組,sizeof(sd)=12;
從上面的分析可以總結出下面規律:
1)結構體中的每個變數自身對齊值與指定或缺省的對齊值進行比較,取兩者較小者作為該成員變數的「有效對齊值」;
2)然後根據 (位址 % n)=0 確定各個成員變數的儲存位址(其中n為有效對齊值);
3)確定各個成員變數的儲存位址後,在看結構體自身對齊值——
其成員中自身對齊值最大的那個值,同樣
取兩者較小者作為該結構體的「有效對齊值」;
再根據
結構體圓整
求得該結構體總共占用了多少位元組的空間大小。
正在向嵌入式進軍,大家互相交流,共同進步!!!
C語言結構體
1.1.1 結構概念 1 結構存在的意義 存在是合理的,許多事物的存在是在不斷解決問題引入的,當然有更好的方法出現時改變也是合理的。在實際問題中,一組資料往往具有不同的資料型別。例如,在學生登記表中,姓名應為字元型,學號可為整型或字元型,年齡應為整型,性別應為字元型,成績可為整型或實型。顯然不能用乙...
C語言 結構體
宣告乙個結構體型別 struct 結構體名 成員表列 定義結構體變數的方法 1 先宣告結構體型別再定義變數名。在定義了結構體變數後,系統會為之分配記憶體單元.例如 struct student student1,student2 2 在宣告型別的同時定義變數,例如 struct 結構體名 成員表列 ...
c語言 結構體
1 定義結構體 c語言允許使用者自己建立不同型別資料組成的組合型的資料結構 struct 結構體名 1 結構體的型別不是只有一種,可以根據需要設計許多種,如struct student struct worker等 2 結構體的成員可以是另一結構體的成員,注意的是引用此成員的方式 2 定義結構體變數...