一丶了解什麼是結構體,以及計算結構體成員的對其值以及總大小(類也是這樣算)
結構體的特性
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彙編代碼一樣.