我是CPU阿甘, 上次我給大家承諾過,要講一講函數(shù)調(diào)用的秘密, 這個確實有點復(fù)雜, 想透徹的理解機器代碼層面的函數(shù)調(diào)用不容易。
我也是從無數(shù)的指令中悟出這個函數(shù)調(diào)用的秘密的, 所以慢慢來,不要急。 放松心情, 慢慢的品味,你可能需要多看幾遍才能明白。
但是你一旦理解了,絕對物超所值,因為你會了解到匯編,寄存器,指針,以及他們在一起到底是怎么工作的。
首先, 一個程序一條一條的指令都的老老實實的放在內(nèi)存的一個地方,這個地方是Linux老大分配的, 我干涉不了, 但是這些指令都是我打電話給硬盤, 讓他給運輸?shù)絻?nèi)存的。然后Linux老大就會告訴我程序的入口點, 其實就是第一條指令的存放地址, 我就打電話問內(nèi)存要這個指令, 取到指令以后就開始執(zhí)行。這些指令當中無非有這么幾類:1. 把數(shù)據(jù)從內(nèi)存加載我的寄存器里什么? 你不知道啥是寄存器? 寄存器就是我內(nèi)部的一個臨時的數(shù)據(jù)存儲空間了2. 對寄存器的數(shù)據(jù)進行運算, 例如把兩個寄存器的數(shù)加起來3. 把我寄存器的數(shù)據(jù)再寫到內(nèi)存里但是我一旦遇到像這樣的指令。"把寄存器ebp的值壓到棧里去“我就知道好戲要上場了, 函數(shù)調(diào)用就會開始。我們這些x86體系的機器有個特點,就是每個函數(shù)調(diào)用都會創(chuàng)建一個所謂的“幀”哈哈, 不要被這些術(shù)語嚇壞, 其實幀也就是我哥們內(nèi)存中的一段連續(xù)的空間而已。像這樣:
現(xiàn)在這個指令來了:"把寄存器ebp的值壓到棧里去“"把esp的值賦給ebp"
"把esp 的值減去24”
“把10放到ebp 減去4的地址” (其實就是796嘛)“把20放到ebp減去8的地址” (其實就是792嘛)
" 把地址796作為數(shù)據(jù)放到 esp指向的地址“ (其實就是776嘛)" 把地址792作為數(shù)據(jù)放到 esp+4指向的地址" (其實就是780嘛)
這其實就相當于把 x 的指針 &x和 y 的指針 &y ,放到了特定的地方, 準備著要做什么事情 , 可能要調(diào)用函數(shù)了。
所以,所謂的指針就是地址而已。
我猜程序員寫的代碼應(yīng)該是這樣:int x = 10;int y = 20;int sum= add(&x, &y);接下來的指令是這樣:“調(diào)用函數(shù) add”我看到這樣的函數(shù)就需要特別小心, 因為我必須要找到 add函數(shù)返回以后的那條指令的地址, 把它也壓到棧里去。int x = 10;int y = 20;int sum = add(&x, &y);printf("the sum is %d\n",sum);假設(shè)這條指令的地址是100
注意啊, 把函數(shù)調(diào)用結(jié)束的以后的返回地址100壓入棧以后, esp 也發(fā)生變化了, 指向了772的位置我會找到函數(shù)Add 的指令,繼續(xù)執(zhí)行"把寄存器ebp的值壓到棧里去“"把esp的值賦給ebp""把寄存器ebx的值壓入?!蹦憧疵總€函數(shù)的開始指令都是這樣, 我猜這應(yīng)該是一種約定吧這里額外把ebx這個寄存器壓入棧, 是因為ebx可能被上個函數(shù)使用, 但是在add函數(shù)中也會用 , 為了不破壞之前的值, 只有先委屈一下暫時放到內(nèi)存里吧。
“把ebp 加8的數(shù)據(jù)取出來放到 edx 寄存器” (ebp+8 不就是地址776嘛, 其中存放的是&x的地址, 這就是取參數(shù)了)“把ebp 加12的數(shù)據(jù)取出來放到 ecx 寄存器” (ebp+12 不就是地址780嘛, 其中存放的是&y的地址)注意啊, 現(xiàn)在edx的值是796, ecx的值是792 , 但他們?nèi)匀徊皇钦嬲臄?shù)據(jù), 而是指針(地址)!“把edx 指向的內(nèi)存地址(796)的數(shù)據(jù)取出來,放到ebx 寄存器”“把ecx 指向的內(nèi)存地址(792)的數(shù)據(jù)取出來,放到eax寄存器”此時此刻, 終于取到了真正的值, ebx = 10, eax = 20你暈了沒有? 如果你到此已經(jīng)暈了, 建議你再讀一遍。 我想源代碼應(yīng)該非常的簡單,就是這樣:int add(int *xp , int *yp){ int x = *xp; int y = *yp; ....}“把ebx 和 eax 的值加起來,放到 eax寄存器中”這個指令我最擅長做了。接下來的指令也很關(guān)鍵, add 函數(shù)已經(jīng)調(diào)用完成, 準備返回了“把esp 指向的數(shù)據(jù)彈出的ebx寄存器”“把esp 指向的數(shù)據(jù)彈出到ebp寄存器”
"返回"我就會取出那個返回地址, 也就是 100, 去這里找指令接著執(zhí)行其實就是這條語句:printf("the sum is %d\n",sum);問你一個問題, sum的值在那里保存著呢?對, 是在eax寄存器里 !搞定了,看著很復(fù)雜, 其實看透了也挺簡單吧。 函數(shù)調(diào)用,關(guān)鍵就是(1)把參數(shù)和返回地址準備好,(2)然后大家都遵循約定, 每次新函數(shù)都要建立新的函數(shù)幀: "把寄存器ebp的值壓到棧里去“ "把esp的值賦給ebp"(3) 函數(shù)調(diào)用完了, 重置 ebp 和esp ,讓他們重新指向調(diào)用著的棧幀。好了,今天就到此為止 , 把我也累壞了, 主人又要關(guān)機了,留一個問題吧:C語言編譯,鏈接以后直接就是機器碼, 那函數(shù)調(diào)用的操作都是上面講的。但是對于Python, Ruby 這樣的解釋型語言, 或者對于java 這樣的有虛擬機的語言, 他們的函數(shù)調(diào)用是什么樣的? 和上面講的有什么關(guān)系?
-
寄存器
+關(guān)注
關(guān)注
31文章
5304瀏覽量
119876 -
匯編
+關(guān)注
關(guān)注
2文章
214瀏覽量
25891 -
函數(shù)調(diào)用
+關(guān)注
關(guān)注
0文章
19瀏覽量
2578
原文標題:CPU阿甘:函數(shù)調(diào)用的秘密
文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論