C語言結構體大小及對齊問題

2021-10-02 15:40:16 字數 4120 閱讀 9488

寫在前面:目錄

一、記憶體大小問題

二、分配問題

三、結構體分配的空間

四、記憶體大小對齊原則

五、其他

有時候,我們在不同的編譯環境,或者不同的機子上測試編譯,會呈現不同的結果,於是我們會陷入疑問,記憶體的大小是誰分配的呢?

在系統中,系統對記憶體的識別是以 byte(位元組)為單位,每個位元組由8位二進位制數組成,即 8bit(位元,也稱「位」)。按照計算機的二進位制方式,1byte=8bit;1kb=1024byte;1mb=1024kb;1gb=1024mb;1tb=1024gb。

我們都知道 byte(位元組)的大小是固定的,但是,我們目前接觸的都是以 word(字長)、halfword(半字),而他的解釋是:在電腦領域,對於某種特定的計算機設計而言,(英語:word)是用於表示其自然的資料單位的術語。在這個特定電腦中,字是其用來一次性處理事務的乙個固定長度的位(bit)組。乙個字的位數(即字長)是電腦系統結構中的乙個重要特性。

我們之所以有可能出現編譯的資料記憶體大小不一樣,是因為在不同的處理器和編譯器中它所呈現的結果是不一樣的,直白的說就是所對應的 word(字長)不一樣,其實字長並非乙個十分嚴格的概念,要從組合語言的角度理解,就是指令集裡面的運算和記憶體操作時運算元的長度,具體還是看一下關於計算機底層的東西。

在 32位處理器中 32位指的就是 cpu gprs(general-purpose registers,通用暫存器)的資料寬度,當然 64位 cpu的資料寬度為 64位,所以 32位cpu的資料寬度指的是 32位了。

64位指令集就是執行 64位資料的指令,也就是說處理器一次可以執行 64bit資料。這樣一來 32位處理器一次最多只能處理 32位,也就是 4個位元組的資料,而 64位處理器一次就能處理 64位,即 8個位元組的資料。

按照上面總的來說:各種型別的儲存大小與系統位數有關

而至於為什麼又跟編譯器有關呢?

假設我們在 64位處理器上執行 32位的編譯器來寫**的,這樣編譯器就只會預設我們的程式是在 32位系統下執行,因為這編譯器最多只能處理 32位,多了它處理不來啊 [哭笑] 

在進行討論之前先來看一下程式

/* 

* 作業系統:基於 x64的處理器

* 編譯環境:dev-c++ v5.11

*/#include #define size 1

struct s ;

struct s temp;

char *p = null;

int main(void)

我們定義了乙個 struct s 的結構體,裡面有不同資料型別的結構體成員 .a /.b /.c /.e /.f(成員 i我們先遮蔽先,這個是關於後面對齊問題測試的);成員 f為陣列,特殊點我們把位址跟大小分開列印;指標我們不知道他的大小,也列印出來;最後我們列印一下輸出看下結果:

先看第一行,我們列印出了 char /short /int /double所對應的資料型別大小;

第二行我們是列印了指標的資料大小,大小為 8byte(因為指標在實質上是乙個記憶體位址,記憶體位址的長度跟 cpu的定址有關;在32位系統上, cpu用 32位表示乙個記憶體位址,這樣的系統上乙個指標佔據 4個位元組(32/8=4);在 64位系統上, cpu用 64位表示乙個記憶體位址,這樣的系統上乙個指標佔據 8個位元組(64/8=8))

第三行是列印出結構體總的大小為 32

再到後面的,當前的結構體位址對應著第乙個結構體成員位址,這個沒問題;後面每個結構體成員所對應的資料型別大小也沒問題;但是,當我們一加起來 1+2+4+8+8+1 = 24 ???跟列印出來的總大小不一樣啊,這就關係到資料對齊了

結構體變數的首位址能夠被其最寬基本型別成員的大小所整除。

結構體每個成員相對於結構體首位址的偏移量(offset)都是成員大小的整數倍,如有需要編譯器會在成員之間加上填充位元組(internal adding)。即結構體成員的末位址減去結構體首位址(第乙個結構體成員的首位址)得到的偏移量都要是對應成員大小的整數倍。

結構體的總大小為結構體最寬基本型別成員大小的整數倍,如有需要編譯器會在成員末尾加上填充位元組。

看了以上的原則後,我們來繼續分析一下上面的資料型別大小問題

實際上:結構體 s = a(1byte)+空閒(1byte)+b(2byte)+c(4byte)+d(8byte)+e(8byte)+f *size(1byte *1)+空閒(7byte)=32(byte)。

解釋一下:上面輸出的結構體變數的首位址為 0x0407a20,對於第乙個成員 a的位址就是結構體的首位址,占用 1個位元組(符合第乙個的原則);因為成員 a只用了 1個位元組,而成員 b位址要 2的倍數,中間隔著乙個位元組,那麼就需要把這個乙個位元組填充進去,為後面的成員 b的位址製造出 2的倍數的位址數);成員 b自己的大小為 2byte,他所對的位址必須是 2的倍數,那麼在填充完那乙個位元組後,他的位址排下去就是 0x0407a22,對應上了 2的倍數(即上面的第二點原則);成員 c大小為 4,同樣的他所對的位址就得要 4的倍數,算一下前面一共占用的位址數:a(1byte)+空閒(1byte)+b(2byte) = 4(byte),當前的位址就已經是 4的倍數了,所以不需要填充;後面兩個成員也一樣;然後再到最後乙個成員 f的分析,因為這是最後乙個成員了,這樣就已經知道在整個結構體 s中他的最寬基本型別成員大小是多小了,沒錯,是 8byte(double型別或者指標的記憶體位址大小);那麼按照第三點的原則,我們先能加起來的先加起來,先不考慮最後一項成員需要填充多少個位元組: a(1byte)+空閒(1byte)+b(2byte)+c(4byte)+d(8byte)+e(8byte)+f(1byte) = 25(byte);25byte明顯不是最寬基本型別成員大小 8byte的倍數了,那麼距離 25最近的 8的倍數有兩個 24和 32,若是選 24,記憶體明顯不夠放,所以只能取大進行填充 7byte,以便總大小成為 32byte

因此簡單總結一下(在 x64位機,64位編譯器中):

型別對齊方式(變數存放的起始位址相對於結構的起始位址的偏移量)

char

偏移量必須為 sizeof(char)即 1的倍數

short

偏移量必須為 sizeof(short)即 2的倍數

int偏移量必須為 sizeof(int)即 4的倍數

float

偏移量必須為 sizeof(float)即 4的倍數

double 

偏移量必須為 sizeof(double)即 8的倍數

各成員變數在存放的時候根據在結構**現的順序依次申請空間,同時按照上面的對齊方式調整位置,空缺的位元組會自動填充。同時為了確保結構的大小為結構的位元組邊界數(即該結構中占用最大空間的型別所占用的位元組數)的倍數,所以在為最後乙個成員變數申請空間後,還會根據需要自動填充空缺的位元組。

案例一(結構體中的成員陣列不指定大小,只限於成員陣列在結構體尾部):

/* 

* 作業系統:基於 x64的處理器

* 編譯環境:dev-c++ v5.11

*/#include #define size 1

struct s ;

struct s temp;

char *p = null;

int main(void)

執行結果:

可以發現在程式中,由於沒有為結構體成員陣列 f指定大小,將不為其分配空間

案例二(在案例一下,再追加乙個結構體成員 i(double型別)):

#include #define size		1

struct s ;

struct s temp;

char *p = null;

int main(void)

這下編譯直接報錯: [error] flexible array member not at end of struct,因為成員陣列 f並不知道他的大

C語言結構體對齊問題

結構體對齊規則 預設對齊方式,按結構體的成員中 size 最大的成員對齊。拋開成員size來說,一般情況下32位機器預設4位元組對齊,64位機器預設8位元組對齊。另外可以使用偽指令 pragma pack n 修改預設的位元組對齊。1.使用偽指令 pragma pack n 編譯器將按照 n 個位元...

結構體大小及記憶體對齊

結構體大小 在計算結構體大小時,有幾點需要注意 1.char可存放在任意位址,short存放在能被2整除的位址,int存放在能被4整除的位址 即資料型別存放的位址要能被其資料型別所佔位元組數整除。可先將每個成員的位元組數寫出,然後從第二個成員開始,將前面成員的總位元組數補齊成當前位元組數的倍數。2....

C 結構體大小及單元空間對齊

運算子sizeof可以計算出給定型別的大小,對於32位系統來說,sizeof char 1 sizeof int 4。基本資料型別的大小很好計算,我們來看一下如何計算構造資料型別的大小。c語言中的構造資料型別有三種 陣列 結構體和共用體。陣列是相同型別的元素的集合,只要會計算單個元素的大小,整個陣列...