天天看點

(C語言完結)結構體在彙編中的表現形式

一丶了解什麼是結構體,以及計算結構體成員的對其值以及總大小(類也是這樣算)

結構體的特性

  1.結構體(struct)是由一系列具有相同類型或不同類型的資料構成的資料集合

  2.在C語言中,結構體(struct)指的是一種資料結構,是C語言中聚合資料類型(aggregate data type)的一類。

結構體可以被聲明為變量、指針或數組等,用以實作較複雜的資料結構。結構體同時也是一些元素的集合,這些元素稱為結構體的成員(member),且這些成員可以為不同的類型,成員一般用名字通路。[1]

進階代碼:

struct TagList

{

char ch;

int number1;

short int number2;

double dbl;

float flt;

};

上面就是一個簡單的結構體,那麼我們這個結構體在記憶體中的偏移要怎麼計算.

公式:

下面是推理,如果不想看可以直接跳到總結去看總結.

  成員偏移量的公式

alg 設alg是編譯器的對其值,offset為結構體首位址的偏移,從0開始.

Member offset % min(alg,sizeof(member type) == 0; 這個公式是求成員位于結構體首位址的偏移

比如計算 成員 flt位與結構體首位址的偏移 ,要先從 第一個成員開始計算

設alg對齊值為4

offset % min(4,sizeof(ch)) == 0;

  0 % min(4,1) == 0 得出ch變量位于結構體首位址為0的偏移處,占1個位元組      +0 1

offset % min(4,sizeof(number1)) == 0

  因為上面求出了ch占的大小,是以求出占1位元組,是以偏移+1變為了1的位置

那麼現在的offset = 1,繼續代入公式

1 % min(4,4) == 0,不成立,偏移繼續++

2%min(4,4) == 0,不成立,偏移繼續++

…..

一直到偏移為4的時候滿足,是以 偏移為4的地方,放number1             +4 4

計算 number2所在的偏移

offset % min(4,sizeof(member type)) == 0;

8 % min(4,2) == 0,成立                             +8 2

計算dbl所在的位置

10 % (4,8) == 0,不成立

11%(4,8) == 0,不成立

12%(4,8) == 0;成立,是以在                           +12 8

計算float的位置

20 % min(4,4) == 0; 成立                          +20 4

那麼各成員的偏移已經計算出來了.

其中float成員位與結構體的 +20偏移,占4個位元組大小.

計算結構體總體大小

  公式:

  sizeof(struct) % min( Max type size,alg);

結構體的大小我們上面計算出來了,是 24個位元組

MAX type,是結構體中最大成員的資料類型大小, 現在是double,也就是8個位元組

alg是編譯器對齊值,現在是4

是以代入公式得到

  24 % 4 == 6…0

是以總體的大小是24個位元組.

總結:

  編譯器對齊值,設定為 alg, MeMber offset 從0開始計算, 其中Member offset 要每次代入公式之後,加上自己成員所占的位元組大小,繼續參與下次運算.

  設定或者檢視編譯器對其值, VC6.0版本 Project (工程) -> Settings(設定) -> C/C++ -> Category(種類) -> Code Generation(代碼生成) -> Struct Member alignment(結構體對齊值)

  結構體成員偏移計算公式: MeMber offset % min(alg,sizeof(Member type)) == 0

  結構體總大小計算公式:   sizeof(struct) % min(Max type size,alg) == 0;

程式記憶體檢視.

根據記憶體視窗指派,可以得出結構體成語位與結構體的偏移是多少

第一個成員, +0 偏移位置, 占1個位元組

第二個成員, +4 偏移位置, 占4個位元組

第三個成員 +8 偏移位置, 占2個位元組

第四個成員 +12偏移位置,占8個位元組

PS: 其中成員的Member offset 從零開始,當計算完畢之後,需要加上自己所占的位元組大小,然後繼續參與運算,如果運算不成立,則偏移繼續增加,一直到偏移成立

比如:

  比如我們計算第二個成員位置的偏移

  Member offset % min(alg,sizeof(member type size) == 0;

  0 % 1 == 0 +0 放第一個成員

Member offset = Mmeber offset + 占的位元組大小,(1)

求第二個成員位置

  1 % 4 ==0; 偏移為1的時候,不成立,則偏移繼續增加

  2 % 4 == 0,不成立繼續增加

  3 % 4 ==0,不成立繼續增加

  4%4 == 0;成立,是以在 +4位置,方放4個位元組,也就是第二個成員位置.

二丶結構體當做參數傳遞,為指針的情況下

void MyFun(struct TagList *pThis)

pThis->ch = ‘b’;

}

int main(int argc, char* argv[])

struct TagList text = {

‘a’,

1,

2,

3.14,

0.0

MyFun(&text);

printf("%drn",text.number1);

return 0;

Debug下的彙編代碼

産生了尋址公式其中eax是數組首位址,ebp +8則是參數,外面傳入的是結構體首位址,是以ebp +8則是數組首

是以 ebp +8 則是結構體的首位址

mov byte ptr[eax],62h 這一句直接産生了 +0位置偏移,取内容指派了字元

mov ecx,[ebp + 8]

mov dword ptr[ecx +4],2 這一句産生了 +4 偏移指派為了2,是以可以确定

1.結構體首位址 ebp + 8 (參數1)

2.結構體第一個成員偏移 +0 指派為字元

3.結構體第二個成員偏移 +4 指派為2

Release下的彙編

main函數調用傳遞結構體位址的時候,隻需要三行彙編

lea eax,[esp + 20h + Var_20]

push eax

call MyFun

上面都是流水線優化的彙編

看下MyFun内部

其結構和Debug差不多

1.獲得結構體的首位址

2.+0偏移位置指派字元

3.+4偏移位置,指派為2

三丶結構體當做參數傳遞,為結構體本身的的情況下

void MyFun(struct TagList pThis)    //這個地方變了.不是指針了

pThis.ch = ‘b’;

pThis.number1 = 2;

MyFun(text);          //傳參不用取位址了

Debug下的彙編

傳參之前的操作

很明顯

1.先擡棧

2.循環6次,每次4個位元組4個位元組的拷貝

3.獲得結構體的首位址

4.将棧頂指派給edi,意思就是說,從棧頂開始複制.

5.執行串操作指令,rep movsd 将 esi的内容複制到棧頂位置處,

因為要複制 24個位元組,是以棧頂要+24是以這一段就是存儲結構體成員的.

MyFun内部

經過傳參之後,esp的位置為數組首位址的,也就是+0位置偏移處

2.進入函數後壓入傳回位址,那麼棧 esp -4, 然後push ebp,繼續esp -4

3.mov ebp,esp,儲存尋址,現在的ebp + 8正好是外面我們進行串拷貝的時候的結構體的首位址.

4.mov byte ptr[ebp +8],62h,相當于就是給我們結構體成員的 +0成員指派

5.mov dword ptr[ebp + 0ch],2 則正好是我們的第二個成員.

是以為了解釋這兩句彙編代碼,需要通過外面傳參的棧環境來看.

和Debug下一樣,也是要進行串拷貝

MyFun函數内部

發現我們沒有使用,是以直接給優化了.

三丶函數傳回值為結構體的時候

1.傳回為指針的時候,直接放到eax中

傳回值,為結構體的情況

三種情況

1.當結構體大小小于(4這個數不确定)個位元組,直接用eax傳回

2.當結構大小小于(8這個數不确定)個位元組,直接用 edx,eax傳回

3.當結構體大小大于 8個位元組以上(不确定,視編譯器而決定).

最後一種的進階代碼:

struct TagList MyFun()

struct TagList text =

3.0,

4.0,

return text;

struct TagList text;

text = MyFun();

printf(“%crn”,text.ch);

1.我們的函數沒有參數,但是Debug會生成上面的代碼,傳入進入, 為什麼? 因為傳回值eax等等都裝不下了,是以利用這塊記憶體區域當做傳回值

2.函數退出之前,也會對它進行串操作指令,因為要傳回這塊記憶體區域,是以寫入記憶體.

3.傳回值以前會把首位址給 eax儲存

4.看外面是否使用eax,如果使用可以可以判斷傳回的是一個對象,(當然這一步可以省略,但是上面的三步少一步都不是傳回對象)

參數問題:

  它會預設給我們生成一個參數傳入,那麼我們有了參數,則會跟在後面.

Release彙編代碼一樣.

繼續閱讀