C結構體中資料的記憶體對齊問題

aFakeProgramer發表於2018-08-20

 

轉自:http://www.cnblogs.com/qwcbeyond/archive/2012/05/08/2490897.html

 

32位機一般預設4位元組對齊(32位機機器字長4位元組),64位機一般預設8位元組對齊(64位機機器字長8位元組) 

 

1.
先看下面的例子:
struct A{
   char c1;
   int i;
   short s;
   int j;
}a;

struct B{
   int i;
   int j;  
   short s;
   char c1;
}b;

結構A沒有遵守位元組對齊原則(為了區分,我將它叫做對齊宣告原則),結構B遵守了。我們來看看在x86上會出現什麼結果。先列印出a和b的各個成員的地址。會看到a中,各個成員間的間距是4個位元組。b中,i和j,j和s都間距4個位元組,但是s和c1間距2個位元組。所以:
sizeof(a) = 16
sizeof(b) = 12
為什麼會有這樣的結果呢?這就是x86上位元組對齊的作用。為了加快程式執行的速度,一些體系結構以對齊的方式設計,通常以字長作為對齊邊界。對於一些結構體變數,整個結構要對齊在內部成員變數最大的對齊邊界,如B,整個結構以4為對齊邊界,所以sizeof(b)為12,而不是11。
對於A來講,雖然宣告的時候沒有對齊,但是根據列印出的地址來看,編譯器已經自動為其對齊了,所以每個成員的間距是4。在x86下,宣告A與B唯一的差別,僅在於A多浪費了4個位元組記憶體。(是不是某些特定情況下,B比A執行更快,這個還需要討論。比如緊挨的兩條分別取s和c1的指令)


如果體系結構是不對齊的,A中的成員將會一個挨一個儲存,從而sizeof(a)為11。顯然對齊更浪費了空間。那麼為什麼要使用對齊呢?
體系結構的對齊和不對齊,是在時間和空間上的一個權衡。對齊節省了時間。假設一個體繫結構的字長為w,那麼它同時就假設了在這種體系結構上對寬度為w的資料的處理最頻繁也是最重要的。它的設計也是從優先提高對w位資料操作的效率來考慮的。比如說讀寫時,大多數情況下需要讀寫w位資料,那麼資料通道就會是w位。如果所有的資料訪問都以w位對齊,那麼訪問還可以進一步加快,因為需要傳輸的地址位減少,定址可以加快。大多數體系結構都是按照字長來對齊訪問資料的。不對齊的時候,有的會出錯,比如MIPS上會產生bus error,而x86則會進行多次訪問來拼接得到的結果,從而降低執行效率。

有些體系結構是必須要求對齊的,如sparc,MIPS。它們在硬體的設計上就強制性的要求對齊。不是因為它們作不到對齊的訪問,而是它們認為這樣沒有意義。它們追求的是速度。
上面講了體系結構的對齊。在IA-32上面,sizeof(a)為16,就是對齊的結果。下面我們來看,為什麼變數宣告的時候也要儘量對齊。
我們看到,結構A的宣告並不對齊,但是它的成員地址仍是以4為邊界對齊的(成員間距為4)。這是編譯器的功勞。因為我所用的編譯器gcc,預設是對齊的。而x86可以處理不對齊的資料訪問,所以這樣宣告程式並不會出錯。但是對於其他結構,只能訪問對齊的資料,而編譯器又不小心設定了不對齊的選項,則程式碼就不能執行了。如果按照B的方式宣告,則不管編譯器是否設定了對齊選項,都能夠正確的訪問資料。

目前的開發普遍比較重視效能,所以對齊的問題,有三種不同的處理方法:
1)     採用B的方式宣告
2)     對於邏輯上相關的成員變數希望放在靠近的位置,就寫成A的方式。有一種做法是顯式的插入reserved成員:
          struct A{
            char c1;
            char reserved1[3];
            int i;
            short s;
            char reserved2[2];
            int j;
}a;
3)     隨便怎麼寫,一切交給編譯器自動對齊。

程式碼中關於對齊的隱患,很多是隱式的。比如在強制型別轉換的時候。下面舉個例子:
unsigned int ui_1=0x12345678;
unsigned char *p=NULL;
unsigned short *us_1=NULL;

p=&ui_1;
*p=0x00;
us_1=(unsigned short *)(p+1);
*us_1=0x0000;
最後兩句程式碼,從奇數邊界去訪問unsigned short型變數,顯然不符合對齊的規定。在x86上,類似的操作只會影響效率,但是在MIPS或者sparc上,可能就是一個bus error(我沒有試)。
有些人喜歡通過移動指標來操作結構中的成員(比如在linux操作struct sk_buff的成員),但是我們看到,A中(&c1+1) 決不等於&i。不過B中(&s+2)就是 &c1了。所以,我們清楚了結構中成員的存放位置,才能編寫無錯的程式碼。同時切記,不管對於結構,陣列,或者普通的變數,在作強制型別轉換時一定要多看看:)不過為了不那麼累,還是遵守宣告對齊原則吧!(這個原則是說變數儘量宣告在它的對齊邊界上,而且在節省空間的基礎上)

2.C/C++函式呼叫方式
我們當然早就知道,C/C++中的函式呼叫,都是以值傳遞的方式,而不是引數傳遞。那麼,值傳遞是如何實現的呢?
函式呼叫前的典型彙編碼如下:
push    %eax
call    0x401394 <test__Fc>
add     $0x10,%esp
首先,入棧的是實參的地址。由於被調函式都是對地址進行操作,所以就能夠理解值傳遞的原理和引數是引用時的情況了。
Call ***, 是要呼叫函式了,後面的地址,就是函式的入口地址。Call指令等價於:
    PUSH IP
    JMP ***
首先把當前的執行地址IP壓棧,然後跳轉到函式執行。
執行完後,被調函式要返回,就要執行RET指令。RET等價於POP IP,恢復CALL之前的執行地址。所以一旦使用CALL指令,堆疊指標SP就會自動減2,因為IP的值進棧了。

函式的引數進棧的順序是從右到左,這是C與其它語言如pascal的不同之處。函式呼叫都以以下語句開始:
push    %ebp
mov     %esp,%ebp
首先儲存BP的值,然後將當前的堆疊指標傳遞給BP。那麼現在BP+2就是IP的值(16位register的情況),BP+4放第一個引數的值,BP+6放第二個引數……。函式在結束前,要執行POP BP。
    
C/C++語言預設的函式呼叫方式,都是由主呼叫函式進行引數壓棧並且恢復堆疊,實參的壓棧順序是從右到左,最後由主調函式進行堆疊恢復。由於主呼叫函式管理堆疊,所以可以實現變參函式。
對於WINAPI和CALLBACK函式,在主呼叫函式中負責壓棧,在被呼叫函式中負責彈出堆疊中的引數,並且負責恢復堆疊。因此不能實現變參函式。

----------------------------------------------------------------------------------------------------------------------------------------------------

結構體的大小是一個令人迷惑不解的問題,不信,我出一題讓你算算看:

enum DataType{IntData,CharData,VcharData};

struct Item    

{

      char ItemNAme[30];

        DataType ItemType;

        char ItemDecr[50];

        int ItemLength;

};

在你使用sizeof之前你能知道它的大小嗎?我想即使你用sizeof得出結果後,結果還是會讓你大吃一驚的:怎麼是這個?

一.為什麼要對齊?

《Windows核心程式設計》裡這樣說:當CPU訪問正確對齊的資料時,它的執行效率最高,當資料大小的資料模數的記憶體地址是0時,資料是對齊的。例如:WORD值應該是總是從被2除盡的地址開始,而DWORD值應該總是從被4除盡的地址開始,資料對齊不是記憶體結構的一部分,而是CPU結構的一部分。當CPU試圖讀取的數值沒有正確的對齊時,CPU可以執行兩種操作之一:產生一個異常條件;執行多次對齊的記憶體訪問,以便讀取完整的未對齊資料,若多次執行記憶體訪問,應用程式的執行速度就會慢。在最好的情況下,是兩倍的時間,有時更長。

二.成員變數對齊的原理

我花了一個上午,看了一些資料,總算把這個問題搞明白了。下面我以一些例子說明結構體成員變數的對齊問題。

對於

struct s1

{

char a;

long int d;

double c;

};

這個結構體的大小是16。編譯器預設的一般是8位元組對齊。a的大小是1,它就按1位元組對齊(因為比指定的8小),存諸在0偏移的地方;b大小為4,它就按4位元組對齊(因為比指定的8小),存在偏移4——7的位置,c大小為8,存在8——15的位置。這樣3個成員共佔用了16個位元組。由於該結構最大成員c大小為8,所以結構按8位元組對齊,16按8園整還是16,因此sizeof s1 = 16.

 

而對於

struct s2

{

char a;

long int d;

double c;

char e;

};

這個結構體的大小是24。前3個成員和上面一樣存諸,d在4——7位置,c在8——15位置,但e按1位元組對齊,存在偏移位置16,這樣4個成員佔用了17個位元組。由於該結構的最大的資料成員c的大小是8個位元組,所以17對8園整得24。

當然你可以使用#pragma指令指定編譯器按4位元組對齊。即

#pragma pack(4)       // 這裡也可以是#pragma pack(push,4)

struct s1

{

char a;

long int d;

double c;

};

struct s2

{

char a;

long int d;

double c;

char e;

};

這時s1的大小還是16,而s2的大小變為20。我們來分析一下,對s1來說,按4位元組對齊和按8位元組對齊個資料成員的存諸位置是一樣的,只不過是最後一部園整時,16對4園整還是16。對s2就不一樣了,a的大小為1(比指定的4位元組對齊要小),按1位元組對齊,存諸在0位置,d的大小是4(大於等於指定的4位元組),按4位元組對齊,存諸在4——7位置,c的大小是8(大於指定的4位元組),按4位元組對齊,這樣存諸在8——15,e的大小是1,儲存在位置16,這樣整個結構體的長度是17,17對4園整,得20。你也看到並不是指定編譯器按4位元組對齊就按4位元組對齊的。比如下面的結構體:

#pragma pack(4)

struct TestStruct2

{

    char m1[11];

    short m2;

};

你知道它的大小嗎?是14。因為m1按1位元組對齊,存諸在0——11位置,m2按2位元組對齊,存諸在12——13位置。結構體佔用13個位元組,因為結構體內最大的成員的資料型別是short,大小為2,比指定的對齊位元組4小,所以13對2園整,得14。綜的說來就是結構體成員的對齊是用成員本身的大小和#pragma pack(push,n)中的n中較小的數對齊,例如如果成員大小為2,而你指定的對齊方式是4,則該成員按2對齊;結構本身的對其是用結構中最大成員的大小和#pragma pack(push,n)中的n較小的數對齊,即最後的園整,例如如果結構中最大成員大小8,而你指定對齊是16,則結構本身按8對齊。

開頭題目的大小是92。你算到了嗎?下面這個結構體的大小又是多少呢?

enum DataType{IntData,CharData,VcharData};

#pragma pack(2)

struct Item    

{

        char ItemNAme[30];

    DataType ItemType;

    char ItemDecr[50];

    int ItemLength;

};

 

----------------------------------------------------------------------------------------------------------------------------------------------------

 

當在C中定義了一個結構型別時,它的大小是否等於各欄位(field)大小之和?編譯器將如何在記憶體中放置這些欄位?ANSI C對結構體的記憶體佈局有什麼要求?而我們的程式又能否依賴這種佈局?這些問題或許對不少朋友來說還有點模糊,那麼本文就試著探究它們背後的祕密。 首先,至少有一點可以肯定,那就是ANSI C保證結構體中各欄位在記憶體中出現的位置是隨它們的宣告順序依次遞增的,並且第一個欄位的首地址等於整個結構體例項的首地址。比如有這樣一個結構體:

struct vector{int x,y,z;} s;
int *p,*q,*r;
struct vector *ps;

p = &s.x;
q = &s.y;
r = &s.z;
ps = &s;
assert(p < q);
assert(p < r);
assert(q < r);
assert((int*)ps == p);
// 上述斷言一定不會失敗
這時,有朋友可能會問:"標準是否規定相鄰欄位在記憶體中也相鄰?"。 唔,對不起,ANSI C沒有做出保證,你的程式在任何時候都不應該依賴這個假設。那這是否意味著我們永遠無法勾勒出一幅更清晰更精確的結構體記憶體佈局圖?哦,當然不是。不過先讓我們從這個問題中暫時抽身,關注一下另一個重要問題————記憶體對齊。
許多實際的計算機系統對基本型別資料在記憶體中存放的位置有限制,它們會要求這些資料的首地址的值是某個數k(通常它為4或8)的倍數,這就是所謂的記憶體對齊,而這個k則被稱為該資料型別的對齊模數(alignment modulus)。當一種型別S的對齊模數與另一種型別T的對齊模數的比值是大於1的整數,我們就稱型別S的對齊要求比T強(嚴格),而稱T比S弱(寬鬆)。這種強制的要求一來簡化了處理器與記憶體之間傳輸系統的設計,二來可以提升讀取資料的速度。比如這麼一種處理器,它每次讀寫記憶體的時候都從某個8倍數的地址開始,一次讀出或寫入8個位元組的資料,假如軟體能保證double型別的資料都從8倍數地址開始,那麼讀或寫一個double型別資料就只需要一次記憶體操作。否則,我們就可能需要兩次記憶體操作才能完成這個動作,因為資料或許恰好橫跨在兩個符合對齊要求的8位元組記憶體塊上。某些處理器在資料不滿足對齊要求的情況下可能會出錯,但是Intel的IA32架構的處理器則不管資料是否對齊都能正確工作。不過Intel奉勸大家,如果想提升效能,那麼所有的程式資料都應該儘可能地對齊。Win32平臺下的微軟C編譯器(cl.exe for 80x86)在預設情況下采用如下的對齊規則: 任何基本資料型別T的對齊模數就是T的大小,即sizeof(T)。比如對於double型別(8位元組),就要求該型別資料的地址總是8的倍數,而char型別資料(1位元組)則可以從任何一個地址開始。Linux下的GCC奉行的是另外一套規則(在資料中查得,並未驗證,如錯誤請指正):任何2位元組大小(包括單位元組嗎?)的資料型別(比如short)的對齊模數是2,而其它所有超過2位元組的資料型別(比如long,double)都以4為對齊模數。
現在回到我們關心的struct上來。ANSI C規定一種結構型別的大小是它所有欄位的大小以及欄位之間或欄位尾部的填充區大小之和。嗯?填充區?對,這就是為了使結構體欄位滿足記憶體對齊要求而額外分配給結構體的空間。那麼結構體本身有什麼對齊要求嗎?有的,ANSI C標準規定結構體型別的對齊要求不能比它所有欄位中要求最嚴格的那個寬鬆,可以更嚴格(但此非強制要求,VC7.1就僅僅是讓它們一樣嚴格)。我們來看一個例子(以下所有試驗的環境是Intel Celeron 2.4G + WIN2000 PRO + vc7.1,記憶體對齊編譯選項是"預設",即不指定/Zp與/pack選項):
typedef struct ms1
{
char a;
int b;
} MS1;
假設MS1按如下方式記憶體佈局(本文所有示意圖中的記憶體地址從左至右遞增):

+---------------------------+
| | |
| a | b |
| | |
+---------------------------+
1 Byte 4 byte 


因為MS1中有最強對齊要求的是b欄位(int),所以根據編譯器的對齊規則以及ANSI C標準,MS1物件的首地址一定是4(int型別的對齊模數)的倍數。那麼上述記憶體佈局中的b欄位能滿足int型別的對齊要求嗎?嗯,當然不能。如果你是編譯器,你會如何巧妙安排來滿足CPU的癖好呢?呵呵,經過1毫秒的艱苦思考,你一定得出瞭如下的方案:
_______________________________________
| |///////////| |
| a |//padding//| b |
| |///////////| |
+-------------------------------------+
Bytes: 1 3 4
這個方案在a與b之間多分配了3個填充(padding)位元組,這樣當整個struct物件首地址滿足4位元組的對齊要求時,b欄位也一定能滿足int型的4位元組對齊規定。那麼sizeof(MS1)顯然就應該是8,而b欄位相對於結構體首地址的偏移就是4。非常好理解,對嗎?現在我們把MS1中的欄位交換一下順序:
typedef struct ms2
{
int a;
char b;
} MS2;
或許你認為MS2比MS1的情況要簡單,它的佈局應該就是
_______________________
| | |
| a | b |
| | |
+---------------------+
Bytes: 4 1 
因為MS2物件同樣要滿足4位元組對齊規定,而此時a的地址與結構體的首地址相等,所以它一定也是4位元組對齊。嗯,分析得有道理,可是卻不全面。讓我們來考慮一下定義一個MS2型別的陣列會出現什麼問題。C標準保證,任何型別(包括自定義結構型別)的陣列所佔空間的大小一定等於一個單獨的該型別資料的大小乘以陣列元素的個數。換句話說,陣列各元素之間不會有空隙。按照上面的方案,一個MS2陣列array的佈局就是:
|<- array[1] ->|<- array[2] ->|<- array[3] .....
__________________________________________________________
| | | | |
| a | b | a | b |.............
| | | | |
+----------------------------------------------------------
Bytes: 4 1 4 1
當陣列首地址是4位元組對齊時,array[1].a也是4位元組對齊,可是array[2].a呢?array[3].a ....呢?可見這種方案在定義結構體陣列時無法讓陣列中所有元素的欄位都滿足對齊規定,必須修改成如下形式:
___________________________________
| | |///////////|
| a | b |//padding//|
| | |///////////|
+---------------------------------+
Bytes: 4 1 3
現在無論是定義一個單獨的MS2變數還是MS2陣列,均能保證所有元素的所有欄位都滿足對齊規定。那麼sizeof(MS2)仍然是8,而a的偏移為0,b的偏移是4。
好的,現在你已經掌握了結構體記憶體佈局的基本準則,嘗試分析一個稍微複雜點的型別吧。
typedef struct ms3
{
char a;
short b;
double c;
} MS3;
我想你一定能得出如下正確的佈局圖:

padding 
|
_____v_________________________________
| |/| |/////////| |
| a |/| b |/padding/| c |
| |/| |/////////| |
+-------------------------------------+
Bytes: 1 1 2 4 8

sizeof(short)等於2,b欄位應從偶數地址開始,所以a的後面填充一個位元組,而sizeof(double)等於8,c欄位要從8倍數地址開始,前面的a、b欄位加上填充位元組已經有4 bytes,所以b後面再填充4個位元組就可以保證c欄位的對齊要求了。sizeof(MS3)等於16,b的偏移是2,c的偏移是8。接著看看結構體中欄位還是結構型別的情況:
typedef struct ms4
{
char a;
MS3 b;
} MS4;
MS3中記憶體要求最嚴格的欄位是c,那麼MS3型別資料的對齊模數就與double的一致(為8),a欄位後面應填充7個位元組,因此MS4的佈局應該是:
_______________________________________
| |///////////| |
| a |//padding//| b |
| |///////////| |
+-------------------------------------+
Bytes: 1 7 16
顯然,sizeof(MS4)等於24,b的偏移等於8。
在實際開發中,我們可以通過指定/Zp編譯選項來更改編譯器的對齊規則。比如指定/Zpn(VC7.1中n可以是1、2、4、8、16)就是告訴編譯器最大對齊模數是n。在這種情況下,所有小於等於n位元組的基本資料型別的對齊規則與預設的一樣,但是大於n個位元組的資料型別的對齊模數被限制為n。事實上,VC7.1的預設對齊選項就相當於/Zp8。仔細看看MSDN對這個選項的描述,會發現它鄭重告誡了程式設計師不要在MIPS和Alpha平臺上用/Zp1和/Zp2選項,也不要在16位平臺上指定/Zp4和/Zp8(想想為什麼?)。改變編譯器的對齊選項,對照程式執行結果重新分析上面4種結構體的記憶體佈局將是一個很好的複習。
到了這裡,我們可以回答本文提出的最後一個問題了。結構體的記憶體佈局依賴於CPU、作業系統、編譯器及編譯時的對齊選項,而你的程式可能需要執行在多種平臺上,你的原始碼可能要被不同的人用不同的編譯器編譯(試想你為別人提供一個開放原始碼的庫),那麼除非絕對必需,否則你的程式永遠也不要依賴這些詭異的記憶體佈局。順便說一下,如果一個程式中的兩個模組是用不同的對齊選項分別編譯的,那麼它很可能會產生一些非常微妙的錯誤。如果你的程式確實有很難理解的行為,不防仔細檢查一下各個模組的編譯選項。
思考題:請分析下面幾種結構體在你的平臺上的記憶體佈局,並試著尋找一種合理安排欄位宣告順序的方法以儘量節省記憶體空間。
A. struct P1 { int a; char b; int c; char d; };
B. struct P2 { int a; char b; char c; int d; };
C. struct P3 { short a[3]; char b[3]; };
D. struct P4 { short a[3]; char *b[3]; };
E. struct P5 { struct P2 *a; char b; struct P1 a[2]; };
參考資料:
【1】《深入理解計算機系統(修訂版)》,
(著)Randal E.Bryant; David O'Hallaron,
(譯)龔奕利 雷迎春,
中國電力出版社,2004

【2】《C: A Reference Manual》(影印版),
(著)Samuel P.Harbison; Guy L.Steele,
人民郵電出版社,2003

 

 

 

-----------------------------------------------------


預設對齊方式:

struct name1
    {
    char   str;     佔用空間:2個位元組
    short x;       佔用空間:2個位元組
    int    num;     佔用空間:4個位元組
    double xx;     佔用空間:8個位元組
    };

struct name2
{
    char str;    佔用空間:4個位元組
    int num;    佔用空間:4個位元組
    short x;    佔用空間:8個位元組
    double xx; 佔用空間:8個位元組
};
struct name1=16個位元組           struct name2=24個位元組

想不明白了,為什麼會如此大的差異!!
大家幫幫忙,解釋一下!!!謝謝大家了!

 

回答1:

這裡必須指出的是你的機器是32位的,而不是SUN SPARC那樣的64位機器。

1. char,short, int,double 各自佔多少位元組
這個很容易得到,他們分別是 1, 2,4,8 
通過下面的語句可以確定在你的系統下是多少,一般 intel 32 位是上面提到的結果
printf("sizeof(char)=%d/n",sizeof(char))
...

2. 如何對齊位元組
32位機器就是按照每32位來對齊,即4位元組。

對於struct name1 內部資料的位元組數分別是1,2,4,8 按照4位元組為單位進行擺放得到 (1,2)(4)(4)(4) //注意1+2不足4位元組,所以放在一起,8位元組的double 用2個位元組表示。每個括號是4位元組,所以得到4*4=16 位元組。

對於struct name2 內部資料的位元組數分別是1,4,2,8,按照4位元組為單位擺放得到(1)(4)(2)(4)(4),注意1,4不能放在相鄰的一起,因為大於4位元組了,所以1位元組的char獨佔4位元組,同樣short 的2 位元組也必須佔用4個位元組。所以得到5*4 = 20 位元組

由此得出 struct name2 佔用20個位元組, 你給出的24位元組有出入。
請使用printf("sizeof(struct name2)=%d",sizeof(struct name2)),檢查一下。

 

回答2:

比如name1,struct中佔用最多空間的型別是double,它佔用8個位元組,所以預設按照8個位元組來進行對齊(即以8個位元組為一個單位),雖然str x name一共佔用了(1+2+4)7個位元組的空間,但是由於下一個型別xx要佔用8個位元組的空間,所以xx只能從下一個8位元組位置開始,因此一共佔用16個位元組。

對於name2,佔用空間最多的還是double型,所以預設還是按照8個位元組來進行對齊,雖然str num一共佔用了(1+4)5個位元組的空間,但是下一個型別要佔用2個位元組,這時看起來好像可以放到上面的空間裡面,但是由於資料預設是從自己所佔用空間的整數倍的位置進行對齊,所以num其實是從第一個8位元組的第4個位元組(從0開始計數)處開始存放,因此x只能放到下一個8位元組空間中,同樣道理xx也要放到再下一個8位元組空間中。

 

回答3:

這個問題應該跟編譯器有關係。
對於結構體中的某一成員item,它相對於結構首地址的實際位元組對齊數目X應該滿足
以下規則:
X = min(n, sizeof(item))。 n 是編譯器設定的最大對齊邊界數。
如果n = 8 .

struct name1
    {
    char   str;     偏移為0 ,從第一個位元組位置儲存,佔1個位元組
    short x;       偏移為2 , 從第三個位元組位置開始儲存,佔2個位元組
    int    num;     偏移為4, 由於前兩個佔了4個位元組,所以從第五個位元組開始儲存。佔4個位元組
    double xx;     偏移為8,由於前兩個佔了8個位元組,所以從第9個位元組開始儲存。佔8個位元組

    };
一共佔16個位元組。
struct name2
{
    char str; 偏移為0 ,從第一個位元組位置儲存,佔1個位元組
    int num;   偏移為4, 由於前兩個佔了1個位元組,所以從第五個位元組開始儲存。佔4個字
    short x;   偏移為2 ,由於前兩個佔了8個位元組 從第九個位元組位置開始儲存,佔2個位元組
    double xx;偏移為8,由於前兩個佔了10個位元組,所以從第17個位元組開始儲存。佔8個位元組

};
一共佔24個位元組。

 

回答4:

struct name2
{
    char str;    佔用空間:4個位元組
    int num;    佔用空間:4個位元組
    short x;    佔用空間:8個位元組//樓主這裡錯了,應該是2個位元組
    double xx; 佔用空間:8個位元組
};
struct name2=24個位元組 //加起來是18個位元組,但是要是sizeof(double)的倍數,所以為24。

 

回答5:

struct name1
    {
    char   str;     佔用空間:2個位元組
    short x;       佔用空間:2個位元組
    int    num;     佔用空間:4個位元組
    double xx;     佔用空間:8個位元組
    };

就拿這個來說 struct 的相對起始地址為0x00000000,char是一位元組,因此short本來應該的地址是0x00000001,但是由於位元組對齊,因此short要在相對起始地址是2的倍數的位置,因此是在0x00000002的位置上了,然後char+short的地址是4位元組,因此int的起始位置是0x00000004,很符合4的倍數,不用動,double xx則正好符合8的倍數

相關文章