1. 程式人生 > >c結構體對齊深刻理解

c結構體對齊深刻理解

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函式,在主呼叫函式中負責壓棧,在被呼叫函式中負責彈出堆疊中的引數,並且負責恢復堆疊。因此不能實現變參函式。

相關推薦

c結構深刻理解

1. 先看下面的例子: struct A{    char c1;    int i;    short s;    int j; }a; struct B{    int i;    int j;      short s;    char c1; }b; 結構A沒有遵守位元組對齊原則(為了區分,我將它叫做

C/C++結構方式詳解,從記憶體地址進行解析

注意:童鞋們如果仔仔細細看完這篇部落格,肯定能明白結構體的對齊方式。 最近在做一個專案的時候,客戶給的鐳射點雲檔案是二進位制形式,因此需要根據客戶定義的結構體,將點雲檔案儲存為文字檔案方便在第三方軟體如cloudCompare中檢視。但是發現客戶的結構體所佔記憶體空間跟我的

C-結構

技術群的筒子們有時候會提到結構體對齊,說實話這個問題還不是幾句話能講清楚的。這個問題網上一搜一大把,已經有無數的前輩總結過。看了很多網上的資料,根據我個人的一些理解,再總結一下,配了些圖片,希望大家能看懂。 首先是結構體對齊規則: 1、對於n個位元組的元素,它的首地址要能

c++結構

參考各資料得出自己使用的公式:1、(當前偏移量+當前填充數)%當前變數大小=02、(總偏移大小+末尾填充數)%最寬變數大小=0必須先滿足1、再滿足2。例如程式碼如下:struct A{int a;//(當前偏移量0+當前填充數0)%當前變數大小4=0char b;// (當前

C/C++結構_思索

       最近在看對齊方面的問題,發現大家在面試筆試的時候,對方基本上都是會拿包含陣列的結構體、或者包含結構體的結構體來考大家,而不會單純的拿幾個int,long,或者double組合在一起考,因為那樣太簡單,找了些資料,幫助自己也幫助大家理解下,共同交流!      

C語言結構(記憶體問題)

C語言結構體對齊也是老生常談的話題了。基本上是面試題的必考題。內容雖然很基礎,但一不小心就會弄錯。寫出一個struct,然後sizeof,你會不會經常對結果感到奇怪?sizeof的結果往往都比你宣告的變數總長度要大,這是怎麼回事呢?     開始學的時候,

C++關於結構

說明: 結構體的sizeof值,並不是簡單的將其中各元素所佔位元組相加,而是要考慮到儲存空間的位元組對齊問題。這些問題在平時程式設計的時候也確實不怎麼用到,但在一些筆試面試題目中出是常常出現,一、解釋 現代計算機中記憶體空間都是按照byte劃分的,從理論上講

C語言結構與不對設定總結

相信不同的編譯平臺間的預設設定差異給大家帶來了很多困擾。在此,僅就結構體對齊解析下之間的差異設定方法。 1.gcc中結構體預設是4個位元組對齊,即為32的倍數。 1.1修改位元組對齊: struct data{ int a; char b; char c; }__

13)結構問題

ngs dmi nts min mage ++ image http c語言 詳細可以看 臺式機的 C:\Documents and Settings\Administrator\桌面\C++基礎教程完整版視頻\01_C語言提高\d

結構

str center 但是 內存 read 地址 形式參數 class area 1 C語言裏可以在一個存儲區裏記錄多個相關數字這種存儲區的類型叫結構體類型,這種類型需要首先創建出來然後才能使用 2 結構體類型存儲區裏包含多個子存儲區,每個子存儲區可以記錄一個數字,結構體中

linux中結構【轉】

src double 無需 fine types 查看 真理 blog 多個 轉自:https://blog.csdn.net/suifengpiao_2011/article/details/47260085 linux中定義對齊字節 typedef struct

逆向基礎之結構

eof 成員對齊 偏移 str 最大 結構體 基礎 數據 sizeof 遵循以下原則,數據成員對齊;結構體大小;結構體有某些成員大,最大對齊,對齊參數筆結構體的sizeof小,偏移以此為準。struct{ char a;int b;char c;}a 1b,補3b 4bc

結構總結

結構體對齊 結構體對齊到底是什麼,看了網上很多的解答,彙總成個人經驗 什麼是結構體對齊 結構體對齊規則 考慮一個問題,為什麼要設計記憶體對齊的處理方式呢? ENDING 結構體對齊到底是什麼,看了網上很多

結構——結構記憶體佈局

在C語言中,可以通過#pragma pack(n)來指定結構體按n位元組對齊(這裡的n是2的較小整數次冪)。如果程式設計者不指定對齊位元組數,那麼預設的會按照結構體中最長那一項對齊,如在64位作業系統中,當結構體中出現(void *),(long)型別,則必然是按照8位元組對齊;當最大的是int,那麼就按照4

結構——結構體內存布局

聲明 amp pri 最大的 結構 sdn int spa turn 在C語言中,可以通過#pragma pack(n)來指定結構體按n字節對齊(這裏的n是2的較小整數次冪)。如果程序設計者不指定對齊字節數,那麽默認的會按照結構體中最長那一項對齊,如在64位操作系統中,當結

sizeof與strlen用法詳解(結構)

#include<stdio.h> int main(int argc,char **argv) { unsigned char a[10] ={1,2,1,2,3,4}; unsigned char a1[10] ={0,0,0,0,0,0}; char *

結構問題.

Intel、微軟等公司曾經出過一道類似的面試題: 2.1 自然對界   struct是一種複合資料型別,其構成元素既可以是基本資料型別(如int、long、float等)的變數,也可以是一些複合資料型別(如array、struct、union等)的資料單元。對於結構體,編譯器會自動進行成員變數的對齊,以提高

結構的原則及原因分析

為什麼要對齊?     現代計算機中記憶體空間都是按照byte劃分的,從理論上講似乎對任何型別的變數的訪問可以從任何地址開始,但實際情況是在訪問特定型別變數的時候經常在特 定的記憶體地址訪問,這就需要各種型別資料按照一定的規則在空間上排列,而不是順序的一個接一個的排放,

64位系統結構訪問段錯誤

先放程式碼: #include struct s { char c; int b; }; int main(void){ printf("sizeof (struct s) = %d.\n

結構問題以及強制型別轉換問題總結

一、什麼是對齊     現在使用的計算機中記憶體空間都是按照位元組劃分的,從理論上講似乎對任何型別的變數的訪問都可以從任何地址開始,但實際上計算機系統對於基本資料型別在記憶體中的存放位置都有限制。舉個例子,一個變數佔用n個位元組,則該變數的起始地址必須能夠被n整除,即存放