久久久久久久999_99精品久久精品一区二区爱城_成人欧美一区二区三区在线播放_国产精品日本一区二区不卡视频_国产午夜视频_欧美精品在线观看免费

 找回密碼
 立即注冊

QQ登錄

只需一步,快速開始

搜索
查看: 3054|回復: 0
打印 上一主題 下一主題
收起左側

一個有趣的變量關聯問題

[復制鏈接]
跳轉到指定樓層
樓主
ID:71922 發表于 2015-1-11 00:12 | 只看該作者 回帖獎勵 |倒序瀏覽 |閱讀模式
(環境是vc6Debug方式下)
#include<stdio.h>
void test()
{
int t;
scanf("%d",&t);
在這里加入代碼
}
main()
{
int m;
test();
printf("m=%d",m);
}

要在test函數中加入代碼,影響main函數里面的變量,很明顯我們要加的代碼是一個賦值語句,語句的右值是我們輸入的t的值,而左值,則是test函數里t變量的地址加上main函數里m變量的地址相對于t變量的偏移的值。

那么問題的實質,就轉變為變量m的地址相對于變量t地址的偏移了。
這個偏移如何求?

基礎知識:
讓我們跳出這個題,看看我們手頭掌握的知識。
函數調用需要用到堆棧,當程序調用函數時,C將函數調用后面的指令地址(稱為返回地址)壓入堆棧。然后,C將函數的參數從右至左依次壓入堆棧。最后,如果函數聲明了局部變量,C將堆棧空間分配給函數以存儲變量的值。
當函數結束的時候,C釋放存儲局部變量和參數的堆棧空間。然后,C根據返回地址判斷下一步要執行的指令。C從堆棧中移走返回值并將地址放入IP寄存器中。
函數調用的堆棧示意圖如下圖所示:




需要說明的是,什么是堆棧呢?我們都知道,變量、常量在內存中的分配大致分成四個區域,分別是堆棧、堆、常數存儲區以及全局、靜態存儲區。分配在堆棧中的變量具有的最主要的特點就是當這個變量不再需要的時候,編譯器會自動回收,函數內的局部變量的存儲單元都可以在棧上分配。
實際上,堆棧是系統內存中的一塊區域,這個區域在操作系統初始化時得到分配。

探究本題:
由堆棧的知識,我們知道了,test函數里的t,以及main函數里的m都是分配在堆棧中的,因此他們之間的地址偏移量,也就取決于函數調用時堆棧是如何操作的。
我們通過匯編代碼來仔細分析下堆棧的操作方式,調試壞境是VC6.0,在windows操作系統中,堆棧的生長方向是由高到低的。

32:
33:
34: main()
35: {
00401070 push ebp
00401071 mov ebp,esp ①
00401073 sub esp,44h ②
00401076 push ebx
00401077 push esi
00401078 push edi
00401079 lea edi,[ebp-44h] ③
0040107C mov ecx,11h
00401081 mov eax,0CCCCCCCCh
00401086 rep stos dword ptr [edi] ④
36: int m;
37: test();
00401088 call @ILT+0(test) (00401005) ⑤
38: printf("m=%d",m);
0040108D mov eax,dword ptr [ebp-4]
00401090 push eax
00401091 push offset string "m=%d" (0042201c)
00401096 call printf (00401160)
0040109B add esp,8 ⑥
39: }
0040109E pop edi
0040109F pop esi
004010A0 pop ebx
004010A1 add esp,44h
004010A4 cmp ebp,esp
004010A6 call __chkesp (00401120)
004010AB mov esp,ebp
004010AD pop ebp
004010AE ret ⑦
--- No source file ----

下圖是到步驟⑤時的堆棧情況,實際上函數main()和test()一樣,也可以看做是函數調用的情況,只不過函數main是由mainCRTStartup這個默認函數調用的,而函數test是由函數main調用的,兩者在調用上的匯編代碼都是一樣的。




好了,堆棧的全貌我們看到了,現在就要真正的開工了。我們結合匯編代碼來看看到底函數調用時,堆棧是如何操作的。
當一切都還沒有發生卻即將發生的時候,是一個令人雞凍的時刻,是時候該向過去告別了,因為我們即將進入一個新的函數,一段新的旅程。但是別急,磨刀不誤砍柴功,有些事情必須先做準備,那么就從這里開始準備吧。
此時,棧頂esp的值是0x12ff84,這個地址上存的值是0x4012c9,我們再去看看0x4012c9位置的指令。很好,在這個指令上一行我們看到了一條醒目的指令:call @ILT+5(main),一切都明白了,0x4012c9就是函數的返回地址。旅行雖然令人興奮,但是我們必須把出口的地址記下來,畢竟,旅行只是生活的調節,我們終將還得繼續自己的生活。

① push ebp
mov ebp, esp
這段代碼很容易,就是把棧底寄存器ebp入棧。ebp主要用于給出堆棧中數據區基址的偏移,從而方便的實現直接存取堆棧中的數據。
mov ebp, esp 就是把當前esp的值賦給ebp。實際上,就是給ebp重新賦了值,該值就是這個main()函數在堆棧中的基址,以后main()函數中分配的局部變量等的地址都會通過ebp算出偏移,來進行讀寫運算。
我們看到0x12ff80地址上存的是0x12ffc0,這是調用main()函數的函數在堆棧中的基址,我們在這里把這個值保存下來,以便退出main()函數時能夠恢復ebp。

② sub esp, 44h
在存儲了ebp,并且重新對ebp賦值了后,代碼將esp減去44h,這個區域是預留給函數的局部變量的。在該函數中定義的局部變量,將依次序在這塊區域中得到分配。
有人說了,萬一這個44h的區域不夠怎么辦,實際上這個44h是可以變化的,編譯器會根據實際情況調整這個數值。我測試了一下,如果定義了1個變量,就會分配44h的空間,定義了2個變量,就會分配48h的空間……依次類推。

③ push ebx
push esi
push edi
lea edi,[ebp-44h]
分配了44h空間后,ebx, esi, edi的值將依次入棧。為什么要先分配44h的空間,再push這三個寄存器,而不是一口氣把ebp, ebx, esi, edi這四個東東全部都壓進去,再分配局部變量的空間呢。
我想是因為這個空間是分配給局部變量的,而所有局部變量的地址都是通過ebp加上偏移量算出來的,因此讓這個空間和ebp緊挨能夠提高運算的效率。
ebx寄存器是基地址寄存器,是四個數據寄存器中唯一可作為存儲器指針使用的寄存器。esi寄存器是源變址寄存器,edi則是目的變址寄存器。這兩個寄存器的典型用法就是進行字符串操作時,esi作為源指針,edi作為目的指針。
lea edi, [ebp-44h] 這一句使得edi的值由0x00000000變成了(0x12ff80 – 44h = )0x12ff3c,這個地址是44h的最后一個地址,至于為什么要賦成這個值,我們馬上就會知道了。

④ mov ecx,11h
mov eax,0CCCCCCCCh
rep stos dword ptr [edi]
我們在②中分配的空間似乎還沒有初始化,現在我們就著手來對這段區域進行初始化。在③中已經把edi的值賦給了這塊空間的一端,而ecx這個計數寄存器里存儲的是循環次數,一共要循環11h次,哦,這個11h就是44h/4得到的。很顯然,eax里存儲的是初值羅,這段話的意思就是,從0x12ff3c這個地址開始,以后的11h個字的值初始化為0xcccccccc。
程序走到這里,基本上函數調用的步驟已經全部完成了,我們總結一下,看看堆棧里到底發生了什么。
壓入了4個寄存器的值,分別是ebp,ebx,esi,edi的值。分配了一個大小為44h的空間,這個空間的大小根據函數的具體情況,由編譯器自行決定。對這個大小為44h的空間的每一個字初始化為0xcccccccc。
OK,我們來算算到目前為止,我們用了堆棧多少空間:0x12ff84 – 0x12ff30 = 54h。

⑤ call @ILT+0(test) (00401005)
做完函數調用的工作,就要開始真正的函數本題的工作了。我們看到int m;這一句C語言并沒有轉化成任何的匯編語言,實際上,這只是一個聲明的語句,告訴編譯器這里有一個變量,叫做m。那么,什么時候分配這個m的空間呢,我們繼續往下看,就會看到一句mov eax,dword ptr [ebp-4],對頭,就在這里,地址是ebp-4,正是44h的最開始的地址。
test()這句話匯編為call @ILT+0(test) (00401005),那么我們又要開始一段新的旅程了。

OK, 我們現在來到了test函數,也離我們需要解答的地方越來越近了。其實我們走到了這里,聰明的你,是不是已經知道了答案了呢?
還是和之前一樣,我們從匯編看起。

2: void test()
3: {
0040DA10 push ebp
0040DA11 mov ebp,esp ⑴
0040DA13 sub esp,44h ⑵
0040DA16 push ebx
0040DA17 push esi
0040DA18 push edi
0040DA19 lea edi,[ebp-44h] ⑶
0040DA1C mov ecx,11h
0040DA21 mov eax,0CCCCCCCCh
0040DA26 rep stos dword ptr [edi] ⑷
4: int t;
5: scanf("%d",&t);
0040DA28 lea eax,[ebp-4]
0040DA2B push eax
0040DA2C push offset string "%d" (00422fd8)
0040DA31 call scanf (004010c0)
0040DA36 add esp,8

7: } ⑸
0040DA3F pop edi
0040DA40 pop esi
0040DA41 pop ebx
0040DA42 add esp,44h
0040DA45 cmp ebp,esp
0040DA47 call __chkesp (00401120)
0040DA4C mov esp,ebp
0040DA4E pop ebp
0040DA4F ret
--- No source file ---

堆棧如下圖所示:





函數的調用和main()函數一樣,而如前所述,局部變量t分配到44h空間的第一個字,也就是0x12ff24這個地址。而局部變量m則分配到main()函數44h空間的第一個字,也就是0x12ff7c中。
OK,我們至此找到了變量t和變量m的偏移,0x12ff7c – 0x12ff24 = 58h,因此我們問題的答案就顯而易見了:
*((&t) + 0x58 / 4) = t;

發散思維:
題目已經解決了,但是我們注意到了,test()函數沒有參數,顯然,我們也很想知道,如果這個函數有參數,堆棧將如何工作。
那么,把題目里的程序改一下子吧:

#include<stdio.h>
void test(int x, char *pStr, double y)
{
int t;
scanf("%d",&t);
*((&t) + 0x58/4) = t;
}

void main()
{
int m;
test(1, "Hello World!", 3.14);
printf("m=%d",m);
}
給test()函數加上了3個參數,分別是int、char *以及double型的,編譯環境依然是Windows操作系統,VC++6.0。

我們來看看關鍵部分的匯編代碼:
14: test(1, "Hello World!", 3.14);
00401088 push 40091EB8h
0040108D push 51EB851Fh ①
00401092 push offset string "Hello World!" (00426028) ②
00401097 push 1 ③
00401099 call @ILT+0(test) (00401005) ④
0040109E add esp,10h ⑤
涉及到這一段代碼的堆棧如下圖:





①②③分別把三個參數壓入棧中,順序是從右向左。
先壓入函數的參數,再壓入返回地址,與基礎知識里講的正好相反,可見壓入的順序是與編譯器有關的。

總結:
在windows操作系統,VC Debug環境下:
1, 函數的局部變量是分配在棧中的,這個區域是將ebp壓入棧后分配的一塊區域。這也是為什么分配在棧上的局部變量在函數結束后會自動收回的原因,因為函數結束后會退棧,因此分配給該函數局部變量的那塊區域也就退棧掉了,自然也就自動收回了。
2, 函數調用時,會自動將ebp, ebx, esi和edi壓入棧,函數的局部變量是通過ebp加上偏移來進行訪問的。
3, 函數的調用是先將參數由右向左壓入棧,再壓入下一條指令的地址作為返回地址。

分享到:  QQ好友和群QQ好友和群 QQ空間QQ空間 騰訊微博騰訊微博 騰訊朋友騰訊朋友
收藏收藏 分享淘帖 頂 踩
回復

使用道具 舉報

您需要登錄后才可以回帖 登錄 | 立即注冊

本版積分規則

手機版|小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術交流QQ群281945664

Powered by 單片機教程網

快速回復 返回頂部 返回列表
主站蜘蛛池模板: 91久久| 超碰人人人 | 亚洲一区二区免费 | 91久久精品国产 | 亚洲性在线 | 成人黄色在线观看 | 欧美成人免费 | 精品一区二区三区四区视频 | 91资源在线观看 | 欧美一级大黄 | 久草免费在线视频 | 秋霞影院一区二区 | 国产精品视频导航 | 一区二区国产精品 | 少妇诱惑av | 麻豆久久久9性大片 | 久久成人av电影 | 日韩二区三区 | 小川阿佐美pgd-606在线 | 午夜精品久久久久久久99黑人 | 国产一二三区电影 | 亚洲一区 | 国产在线二区 | 男女啪啪网址 | 91高清免费| 欧美亚洲国语精品一区二区 | www.99久久.com | 97国产精品视频 | 日本理论片好看理论片 | 国产精品久久久久久影院8一贰佰 | 亚洲精品福利在线 | 伊人超碰| 午夜精品一区二区三区在线观看 | 国产中文区二幕区2012 | 免费看日韩视频 | 国产高清精品一区二区三区 | 在线看av网址 | 亚洲激情第一页 | 亚洲精品乱码久久久久久9色 | 888久久久 | 蜜桃臀av一区二区三区 |