乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      CPU阿甘:函數(shù)調(diào)用的秘密

       西北望msm66g9f 2018-05-24
      我是CPU阿甘, 上次我給大家承諾過,要講一講函數(shù)調(diào)用的秘密, 這個確實(shí)有點(diǎn)復(fù)雜, 想透徹的理解機(jī)器代碼層面的函數(shù)調(diào)用不容易。

      我也是從無數(shù)的指令中悟出這個函數(shù)調(diào)用的秘密的,  所以慢慢來,不要急。 放松心情, 慢慢的品味, 你可能需要多看幾遍才能明白。


      但是你一旦理解了,絕對物超所值,因?yàn)槟銜私獾絽R編,寄存器,指針,以及他們在一起到底是怎么工作的。

      首先, 一個程序一條一條的指令都的老老實(shí)實(shí)的放在內(nèi)存的一個地方,這個地方是Linux老大分配的, 我干涉不了, 但是這些指令都是我打電話給硬盤, 讓他給運(yùn)輸?shù)絻?nèi)存的。 

      然后Linux老大就會告訴我程序的入口點(diǎn), 其實(shí)就是第一條指令的存放地址,  我就打電話問內(nèi)存要這個指令, 取到指令以后就開始執(zhí)行。
      這些指令當(dāng)中無非有這么幾類:
      1. 把數(shù)據(jù)從內(nèi)存加載我的寄存器里
      什么? 你不知道啥是寄存器?  寄存器就是我內(nèi)部的一個臨時的數(shù)據(jù)存儲空間了
      2. 對寄存器的數(shù)據(jù)進(jìn)行運(yùn)算, 例如把兩個寄存器的數(shù)加起來
      3. 把我寄存器的數(shù)據(jù)再寫到內(nèi)存里

      但是我一旦遇到像這樣的指令。 
      '把寄存器ebp的值壓到棧里去“
      我就知道好戲要上場了, 函數(shù)調(diào)用就會開始。 

      我們這些x86體系的機(jī)器有個特點(diǎn),就是每個函數(shù)調(diào)用都會創(chuàng)建一個所謂的“幀”

      哈哈, 不要被這些術(shù)語嚇壞, 其實(shí)幀也就是我哥們內(nèi)存中的一段連續(xù)的空間而已。
      像這樣: 
      多個函數(shù)幀在內(nèi)存里排起來, 就像一個先進(jìn)后出的棧一樣, 不過,這個棧不像我們常見的棧, 棧底在下面。
      相反,這個棧的棧底在上面, 是從上往下生長的 (或者說是從高地址向低地址生長的)

      內(nèi)存經(jīng)常向我抱怨: '阿甘,你知道嗎, 每次我看到這個棧, 都有一種真氣逆行的感覺, 半天都調(diào)整不過來 ' 

      但內(nèi)存不知道, 我有一個叫ebp的特殊寄存器, 一直會指向當(dāng)前函數(shù)在一個棧的開始地址。 
      我還有另外一個特殊寄存器,叫做esp , 他會隨著指令的運(yùn)行,指向函數(shù)幀的最后的地址, 像這樣:
      現(xiàn)在這個指令來了:
      '把寄存器ebp的值壓到棧里去“
      '把esp的值賦給ebp'

      你看看, 是不是新的函數(shù)幀生成了?
      只不過現(xiàn)在只有一行數(shù)據(jù)。 ebp和esp指向同一地址。
      函數(shù)幀的第一行的地址是800,  里邊的內(nèi)容是1000, 也就是上個函數(shù)幀的地址

      注意, 我們每次操作的是4個字節(jié),所以原來esp 的地址是804, 現(xiàn)在變成了800
      我又問內(nèi)存要下一條指令:
      '把esp 的值減去24”

      下面幾條指令是這樣的:
      “把10放到ebp 減去4的地址” (其實(shí)就是796嘛)
      “把20放到ebp減去8的地址” (其實(shí)就是792嘛)

      你們知道這是干什么嗎? 
      我想了好久才明白這是干嘛, 這其實(shí)就是在分配函數(shù)的局部變量啊
      我猜源代碼應(yīng)該是這樣的:
      int x = 10;
      int y = 20;
      在我看來, x, y 只是變量, 他們叫什么根本不重要, 重要的是他們的值和地址!
      下面幾條指令很有意思:
      ' 把地址796作為數(shù)據(jù)放到 esp指向的地址“ (其實(shí)就是776嘛)
      ' 把地址792作為數(shù)據(jù)放到 esp+4指向的地址' (其實(shí)就是780嘛)
      這又是在干嘛?

      這其實(shí)就相當(dāng)于把 x 的指針 &x和 y 的指針 &y ,放到了特定的地方, 準(zhǔn)備著要做什么事情 , 可能要調(diào)用函數(shù)了。

      所以,所謂的指針就是地址而已。

      我猜程序員寫的代碼應(yīng)該是這樣:
      int x = 10;
      int y = 20;
      int sum= add(&x, &y); 
      接下來的指令是這樣:
      “調(diào)用函數(shù) add”
      我看到這樣的函數(shù)就需要特別小心, 因?yàn)槲冶仨氁业?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的值壓入?!?/span>
      你看每個函數(shù)的開始指令都是這樣, 我猜這應(yīng)該是一種約定吧
      這里額外把ebx這個寄存器壓入棧, 是因?yàn)閑bx可能被上個函數(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)用完成, 準(zhǔn)備返回了 
       “把esp 指向的數(shù)據(jù)彈出的ebx寄存器”
      “把esp 指向的數(shù)據(jù)彈出到ebp寄存器”

      你看add 函數(shù)幀已經(jīng)消失了, 或者換句話說, add 函數(shù)幀的數(shù)據(jù)還在內(nèi)存里, 只是我們不在關(guān)心了!
      接下來的指令非常的關(guān)鍵:
      '返回'
      我就會取出那個返回地址, 也就是 100, 去這里找指令接著執(zhí)行
      其實(shí)就是這條語句: printf('the sum is %d\n',sum);
      問你一個問題, sum的值在那里保存著呢? 
      對, 是在eax寄存器里 !

      搞定了,看著很復(fù)雜, 其實(shí)看透了也挺簡單吧。 函數(shù)調(diào)用,關(guān)鍵就是
      (1)把參數(shù)和返回地址準(zhǔn)備好, 
      (2)然后大家都遵循約定, 每次新函數(shù)都要建立新的函數(shù)幀:
         '把寄存器ebp的值壓到棧里去“
          '把esp的值賦給ebp'
      (3) 函數(shù)調(diào)用完了, 重置 ebp 和esp ,讓他們重新指向調(diào)用著的棧幀。

      好了,今天就到此為止 , 把我也累壞了,  主人又要關(guān)機(jī)了, 留一個問題吧: 

      C語言編譯,鏈接以后直接就是機(jī)器碼, 那函數(shù)調(diào)用的操作都是上面講的。 
      但是對于Python, Ruby 這樣的解釋型語言, 或者對于java 這樣的有虛擬機(jī)的語言, 他們的函數(shù)調(diào)用是什么樣的?  和上面講的有什么關(guān)系?

      (完)

        本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報(bào)。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多