程序是怎么运行的?
程序是怎么運行的? 也許我們習(xí)慣了在IDE環(huán)境中敲代碼、執(zhí)行程序,只需一個鍵就能完成從編譯、匯編、鏈接到顯示結(jié)果的所有工作。。
接下來我把它編譯成匯編代碼。注意:在Unix/Linux下使用的是AT&T格式的匯編語言,而Dos/Windows下是Intel風(fēng)格的匯編。這兩者在語法格式上還是有很大的不同的。由于這里是在Linux系統(tǒng)上編譯的,所以產(chǎn)生的是AT&T格式的匯編。 將上面的代碼保存為main.c。下面對main.c進行編譯:gcc?–S?–o?main.s?main.c?-m32 這里的-S表示輸出匯編代碼(如果不加的話就直接生產(chǎn)目標文件了)。后面的-m32表示生成32位的匯編代碼。 執(zhí)行這條命令后,產(chǎn)生了如下匯編代碼main.s: 注意:生成的匯編代碼會有一些其它的信息,這里我把它們刪了,我們只分析和我們的C代碼密切相關(guān)的部分。 我們知道,系統(tǒng)會為每個進程分配一個堆棧空間(關(guān)于進程的堆棧空間的詳細介紹請參考《Unix環(huán)境高級編程》): 程序中的變量包括函數(shù)調(diào)用占用的都是棧的空間,它的地址是向下遞減的。那么初始化棧假設(shè)是這樣的: 這里為了簡化問題,假設(shè)棧底地址為100,由于地址向下遞減,所以依次是96,92,88..... %ebp和%esp是寄存器,其中%ebp存放的是棧底指針,而%esp存放的是棧頂指針。 首先從main函數(shù)開始執(zhí)行: pushl %ebp ? ? ? 表示把%ebp寄存器中的內(nèi)容壓入堆棧,這時%esp就指向地址96處了(棧頂指針自動減4,因為是按32位尋址的。。)。 movl %esp,%ebp 表示將棧頂指針賦值給棧底指針,那么這時%ebp就是96了。 subl $24,%esp 將棧頂指針減去24,這時%esp指向72. movl $1,-12(%ebp) movl $2,-8(%ebp) 這兩條指令表示把立即數(shù)1和2分別放置到(%ebp)減去12和減去8的位置上。 movl -8(%ebp),%eax movl %eax,4(%esp) 表示把2賦給%eax,然后再放置在(%esp)+4的位置上。下面兩天指令也相同。發(fā)現(xiàn)沒?這里就是在給函數(shù)add()傳遞參數(shù)呢。。 此時,棧的情況如下: 接下來執(zhí)行: call add call指令開始調(diào)用add函數(shù),它的作用相當(dāng)于下面兩天指令: pushl %eip
movel add %eip 其中%eip寄存器存放的是當(dāng)前執(zhí)行的指令的地址。這里調(diào)用了call以后,相當(dāng)于把當(dāng)前指令的地址壓入棧中,此時%esp指向68,然后把add函數(shù)要執(zhí)行的指令的地址賦給%eip。 接下來開始執(zhí)行add函數(shù)中的指令,流程和上面的一樣。 這時又用pushl %ebp把棧底指針入棧,你可能會有疑問,為什么每次調(diào)用一個函數(shù)都需要把%ebp入棧呢?其實你會發(fā)現(xiàn),當(dāng)add函數(shù)執(zhí)行到后來,有一個popl %ebp指令,這個指令相當(dāng)于把原來入棧的%ebp又恢復(fù)原值了,這個時候的%ebp指向的就是main函數(shù)的棧底了(相當(dāng)于又回到main函數(shù)的環(huán)境了)。當(dāng)然add函數(shù)里還有一個ret,ret指令代表popl %eip,就是把原來的%eip有恢復(fù)原值了,這樣代碼執(zhí)行流就回到了main函數(shù)中調(diào)用add函數(shù)的下一條指令了。 當(dāng)main函數(shù)執(zhí)行完movl %eax,-4(%ebp)后,開始執(zhí)行l(wèi)eave,leave指令相當(dāng)于如下兩條指令: mov %ebp,%esp
pop %ebp 發(fā)現(xiàn)了嗎?這里相當(dāng)于恢復(fù)調(diào)用main函數(shù)之前的棧情況了,借著執(zhí)行ret后就回到原先的指令流了。這時候棧就恢復(fù)到最開始的狀態(tài)啦。。簡單吧?關(guān)于函數(shù)調(diào)用過程中棧空間的變化情況現(xiàn)在了解一點了嗎? posted on 2015-03-24 21:50 愚&道 閱讀(...) 評論(...) 編輯 收藏
那么你有沒有疑惑過,當(dāng)你執(zhí)行一個簡單的C程序時,它內(nèi)部到底發(fā)生了什么呢?下面我們就從匯編語言的層面上來分析一下程序運行的全過程。
假設(shè)我寫了一個簡單的a+b的程序:接下來我把它編譯成匯編代碼。注意:在Unix/Linux下使用的是AT&T格式的匯編語言,而Dos/Windows下是Intel風(fēng)格的匯編。這兩者在語法格式上還是有很大的不同的。由于這里是在Linux系統(tǒng)上編譯的,所以產(chǎn)生的是AT&T格式的匯編。 將上面的代碼保存為main.c。下面對main.c進行編譯:
movel add %eip 其中%eip寄存器存放的是當(dāng)前執(zhí)行的指令的地址。這里調(diào)用了call以后,相當(dāng)于把當(dāng)前指令的地址壓入棧中,此時%esp指向68,然后把add函數(shù)要執(zhí)行的指令的地址賦給%eip。 接下來開始執(zhí)行add函數(shù)中的指令,流程和上面的一樣。 這時又用pushl %ebp把棧底指針入棧,你可能會有疑問,為什么每次調(diào)用一個函數(shù)都需要把%ebp入棧呢?其實你會發(fā)現(xiàn),當(dāng)add函數(shù)執(zhí)行到后來,有一個popl %ebp指令,這個指令相當(dāng)于把原來入棧的%ebp又恢復(fù)原值了,這個時候的%ebp指向的就是main函數(shù)的棧底了(相當(dāng)于又回到main函數(shù)的環(huán)境了)。當(dāng)然add函數(shù)里還有一個ret,ret指令代表popl %eip,就是把原來的%eip有恢復(fù)原值了,這樣代碼執(zhí)行流就回到了main函數(shù)中調(diào)用add函數(shù)的下一條指令了。 當(dāng)main函數(shù)執(zhí)行完movl %eax,-4(%ebp)后,開始執(zhí)行l(wèi)eave,leave指令相當(dāng)于如下兩條指令: mov %ebp,%esp
pop %ebp 發(fā)現(xiàn)了嗎?這里相當(dāng)于恢復(fù)調(diào)用main函數(shù)之前的棧情況了,借著執(zhí)行ret后就回到原先的指令流了。這時候棧就恢復(fù)到最開始的狀態(tài)啦。。簡單吧?關(guān)于函數(shù)調(diào)用過程中棧空間的變化情況現(xiàn)在了解一點了嗎? posted on 2015-03-24 21:50 愚&道 閱讀(...) 評論(...) 編輯 收藏
轉(zhuǎn)載于:https://www.cnblogs.com/yudao/p/4364124.html
總結(jié)
- 上一篇: 我对NHibernate的感受(2):何
- 下一篇: python 在 eclipse 上的