• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            posts - 319, comments - 22, trackbacks - 0, articles - 11
              C++博客 :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

            1 什么是堆棧

            編譯器一般使用堆棧實(shí)現(xiàn)函數(shù)調(diào)用。堆棧是存儲器的一個(gè)區(qū)域,嵌入式環(huán)境有時(shí)需要程序員自己定義一個(gè)數(shù)組作為堆棧。Windows為每個(gè)線程自動維護(hù)一個(gè)堆棧,堆棧的大小可以設(shè)置。編譯器使用堆棧來堆放每個(gè)函數(shù)的參數(shù)、局部變量等信息。

            函數(shù)調(diào)用經(jīng)常是嵌套的,在同一時(shí)刻,堆棧中會有多個(gè)函數(shù)的信息,每個(gè)函數(shù)占用一個(gè)連續(xù)的區(qū)域。一個(gè)函數(shù)占用的區(qū)域被稱作幀(frame)。

            編譯器從高地址開始使用堆棧。 假設(shè)我們定義一個(gè)數(shù)組a[1024]作為堆棧空間,一開始棧頂指針指向a[1023]。如果棧里有兩個(gè)函數(shù)a和b,且a調(diào)用了b,棧頂指針會指向函數(shù)b的幀。如果函數(shù)b返回。棧頂指針就指向函數(shù)a的幀。如果在棧里放了太多東西造成溢出,破壞的是a[0]上面的東西。

            在多線程(任務(wù))環(huán)境,CPU的堆棧指針指向的存儲器區(qū)域就是當(dāng)前使用的堆棧。切換線程的一個(gè)重要工作,就是將堆棧指針設(shè)為當(dāng)前線程的堆棧棧頂?shù)刂贰?/p>

            不同CPU,不同編譯器的堆棧布局、函數(shù)調(diào)用方法都可能不同,但堆棧的基本概念是一樣的。

            2 函數(shù)調(diào)用約定

            函數(shù)調(diào)用約定包括傳遞參數(shù)的順序,誰負(fù)責(zé)清理參數(shù)占用的堆棧等,例如 :

             參數(shù)傳遞順序誰負(fù)責(zé)清理參數(shù)占用的堆棧
            __pascal從左到右調(diào)用者
            __stdcall從右到左被調(diào)函數(shù)
            __cdecl從右到左調(diào)用者

            調(diào)用函數(shù)的代碼和被調(diào)函數(shù)必須采用相同的函數(shù)的調(diào)用約定,程序才能正常運(yùn)行。在Windows上,__cdecl是C/C++程序的缺省函數(shù)調(diào)用約定。

            在有的cpu上,編譯器會用寄存器傳遞參數(shù),函數(shù)使用的堆棧由被調(diào)函數(shù)分配和釋放。這種調(diào)用約定在行為上和__cdecl有一個(gè)共同點(diǎn):實(shí)參和形參數(shù)目不符不會導(dǎo)致堆棧錯(cuò)誤。

            不過,即使用寄存器傳遞參數(shù),編譯器在進(jìn)入函數(shù)時(shí),還是會將寄存器里的參數(shù)存入堆棧指定位置。參數(shù)和局部變量一樣應(yīng)該在堆棧中有一席之地。參數(shù)可以被理解為由調(diào)用函數(shù)指定初值的局部變量。

            3 例子:__cdecl和__stdcall

            不同的CPU,不同的編譯器,堆棧的布局可能是不同的。本文以x86,VC++的編譯器為例。

            VC++編譯器的已經(jīng)不再支持__pascal, __fortran, __syscall等函數(shù)調(diào)用約定。目前只支持__cdecl和__stdcall。

            采用__cdecl或__stdcall調(diào)用方式的程序,在剛進(jìn)入子函數(shù)時(shí),堆棧內(nèi)容是一樣的。esp指向的棧頂是返回地址。這是被call指令壓入堆棧的。下面是參數(shù),左邊參數(shù)在上,右邊參數(shù)在下(先入棧)。

            如前表所示,__cdecl和__stdcall的區(qū)別是:__cdecl是調(diào)用者清理參數(shù)占用的堆棧,__stdcall是被調(diào)函數(shù)清理參數(shù)占用的堆棧。

            由于__stdcall的被調(diào)函數(shù)在編譯時(shí)就必須知道傳入?yún)?shù)的準(zhǔn)確數(shù)目(被調(diào)函數(shù)要清理堆棧),所以不能支持變參數(shù)函數(shù),例如printf。而且如果調(diào)用者使用了不正確的參數(shù)數(shù)目,會導(dǎo)致堆棧錯(cuò)誤。

            通過查看匯編代碼,__cdecl函數(shù)調(diào)用在call語句后會有一個(gè)堆棧調(diào)整語句,例如:

              a = 0x1234;
              b = 0x5678;
              c = add(a, b);

            對應(yīng)x86匯編:

              mov dword ptr [ebp-4],1234h
              mov dword ptr [ebp-8],5678h
              mov eax,dword ptr [ebp-8]
              push eax
              mov ecx,dword ptr [ebp-4]
              push ecx
              call 0040100a
              add esp,8
              mov dword ptr [ebp-0Ch],eax


            __stdcall的函數(shù)調(diào)用則不需要調(diào)整堆棧:

              call 00401005
              mov dword ptr [ebp-0Ch],eax

            函數(shù)

              int __cdecl add(int a, int b)
              {
              return a+b;
              }

            產(chǎn)生以下匯編代碼(Debug版本):

              push ebp
              mov ebp,esp
              sub esp,40h
              push ebx
              push esi
              push edi
              lea edi,[ebp-40h]
              mov ecx,10h
              mov eax,0CCCCCCCCh
              rep stos dword ptr [edi]
              mov eax,dword ptr [ebp+8]
              add eax,dword ptr [ebp+0Ch]
              pop edi
              pop esi
              pop ebx
              mov esp,ebp
              pop ebp
              ret // 跳轉(zhuǎn)到esp所指地址,并將esp+4,使esp指向進(jìn)入函數(shù)時(shí)的第一個(gè)參數(shù)

            再查看__stdcall函數(shù)的實(shí)現(xiàn),會發(fā)現(xiàn)與__cdecl函數(shù)只有最后一行不同:

              ret 8 // 執(zhí)行ret并清理參數(shù)占用的堆棧

            對于調(diào)試版本,VC++編譯器在“直接調(diào)用地址”時(shí)會增加檢查esp的代碼,例如:

              ta = (TAdd)add; // TAdd定義:typedef int (__cdecl *TAdd)(int a, int b);
              c = ta(a, b);

            產(chǎn)生以下匯編代碼:

              mov [ebp-10h],0040100a
              mov esi,esp
              mov ecx,dword ptr [ebp-8]
              push ecx
              mov edx,dword ptr [ebp-4]
              push edx
              call dword ptr [ebp-10h]
              add esp,8
              cmp esi,esp
              call __chkesp (004011e0)
              mov dword ptr [ebp-0Ch],eax

            __chkesp 代碼如下。如果esp不等于函數(shù)調(diào)用前保存的值,就會轉(zhuǎn)到錯(cuò)誤處理代碼。

              004011E0 jne __chkesp+3 (004011e3)
              004011E2 ret
              004011E3 ;錯(cuò)誤處理代碼

            __chkesp的錯(cuò)誤處理會彈出對話框,報(bào)告函數(shù)調(diào)用造成esp值不正確。 Release版本的匯編代碼要簡潔得多。也不會增加 __chkesp。如果發(fā)生esp錯(cuò)誤,程序會繼續(xù)運(yùn)行,直到“遇到問題需要關(guān)閉”。

            3 補(bǔ)充說明

            函數(shù)調(diào)用約定只是“調(diào)用函數(shù)的代碼”和被調(diào)用函數(shù)之間的關(guān)系。

            假設(shè)函數(shù)A是__stdcall,函數(shù)B調(diào)用函數(shù)A。你必須通過函數(shù)聲明告訴編譯器,函數(shù)A是__stdcall。編譯器自然會產(chǎn)生正確的調(diào)用代碼。

            如果函數(shù)A是__stdcall。但在引用函數(shù)A的地方,你卻告訴編譯器,函數(shù)A是__cdecl方式,編譯器產(chǎn)生__cdecl方式的代碼,與函數(shù)A的調(diào)用約定不一致,就會發(fā)生錯(cuò)誤。

            以delphi調(diào)用VC函數(shù)為例,delphi的函數(shù)缺省采用__pascal約定,VC的函數(shù)缺省采用__cdecl約定。我們一般將VC的函數(shù)設(shè)為__stdcall,例如:

              int __stdcall add(int a, int b);

            在delphi中將這個(gè)函數(shù)也聲明為__stdcall,就可以調(diào)用了:

              function add(a: Integer; b: Integer): Integer;
              stdcall; external 'a.dll';

            因?yàn)榭紤]到可能被其它語言的程序調(diào)用,不少API采用__stdcall的調(diào)用約定。

            日本精品久久久中文字幕| 久久国产香蕉一区精品| 国产成人99久久亚洲综合精品| 欧美一级久久久久久久大片| 久久国产精品无码一区二区三区 | 久久久久无码国产精品不卡| 欧美黑人激情性久久| 亚洲国产婷婷香蕉久久久久久| 久久久久无码国产精品不卡| 国产农村妇女毛片精品久久| 国产精品99久久精品爆乳| 久久强奷乱码老熟女| 久久久久亚洲爆乳少妇无| 日批日出水久久亚洲精品tv| 久久精品国产男包| 久久久无码精品亚洲日韩按摩| 日韩精品久久久久久免费| 欧美黑人又粗又大久久久| 99久久成人国产精品免费| 久久精品亚洲欧美日韩久久| 一本色道久久88综合日韩精品| 久久人人爽人人爽人人爽| 久久精品人人做人人妻人人玩| 国产人久久人人人人爽| 中文字幕一区二区三区久久网站| 久久精品中文字幕一区| 久久午夜福利无码1000合集| 日韩人妻无码精品久久免费一 | 九九久久精品无码专区| 久久成人国产精品一区二区| 一本色综合久久| 国产精品久久久福利| 亚洲国产精品嫩草影院久久 | 韩国三级大全久久网站| 久久久人妻精品无码一区| 久久无码人妻一区二区三区 | 午夜精品久久久久久久无码| 精品久久久中文字幕人妻| 国产精品美女久久久m| 久久精品国产精品亚洲艾草网美妙| 久久精品国产精品亚洲精品|