C++调用约定
什么是調用約定
函數的調用約定,顧名思義就是對函數調用的一個約束和規定(規范),描述了函數參數是怎么傳遞和由誰清除堆棧的。它決定以下內容:(1)函數參數的壓棧順序,(2)由調用者還是被調用者把參數彈出棧,(3)以及產生函數修飾名的方法。
我們知道函數由以下幾部分構成:返回值類型 函數名(參數列表),如:
【code1】
以上是大家所熟知的構成部分,其實函數的構成還有一部分,那就是調用約定。如下:
【code2】
上面的__cdecl和__stdcall就是調用約定,其中__cdecl是C和C++默認的調用約定,所以通常我們的代碼都如 【code1】中那樣定義,編譯器默認會為我們使用__cdecl調用約定。常見的調用約定有__cdecl、__stdcall、fastcall,應用最廣泛的是__cdecl和__stdcall,下面我們會詳細進行講述。。還有一些不常見的,如 __pascal、__thiscall、__vectorcall。
聲明和定義處調用約定必須要相同
在VC++中,調用約定是函數類型的一部分,因此函數的聲明和定義處調用約定要相同,不能只在聲明處有調用約定,而定義處沒有或與聲明不同。如下:
【code3】 錯誤的使用一:
報錯:
error C2373: ‘add’: redefinition; different type modifiers
error C2440: ‘initializing’: cannot convert from ‘int (__stdcall *)(int,int)’ to ‘int’
【code4】 錯誤的使用二:
int add(int a, int b); int __stdcall add(int a, int b) {return a + b; }報錯:
error C2373: ‘add’: redefinition; different type modifiers
error C2440: ‘initializing’: cannot convert from ‘int (__cdecl *)(int,int)’ to ‘int’
【code5】 錯誤的使用三:
int __stdcall add(int a, int b); int __cdecl add(int a, int b) {return a + b; }報錯:
error C2373: ‘add’: redefinition; different type modifiers
error C2440: ‘initializing’: cannot convert from ‘int (__stdcall *)(int,int)’ to ‘int’
【code6】 正確的用法:
int __stdcall add(int a, int b); int __stdcall add(int a, int b) {return a + b; }函數的調用過程
要深入理解函數調用約定,你須要了解函數的調用過程和調用細節。
假設函數A調用函數B,我們稱A函數為”調用者”,B函數為“被調用者”。如下面的代碼,ShowResult為調用者,add為被調用者。
函數調用過程可以這么描述:
(1)先將調用者(A)的堆棧的基址(ebp)入棧,以保存之前任務的信息。
(2)然后將調用者(A)的棧頂指針(esp)的值賦給ebp,作為新的基址(即被調用者B的棧底)。
(3)然后在這個基址(被調用者B的棧底)上開辟(一般用sub指令)相應的空間用作被調用者B的棧空間。
(4)函數B返回后,從當前棧幀的ebp即恢復為調用者A的棧頂(esp),使棧頂恢復函數B被調用前的位置;然后調用者A再從恢復后的棧頂可彈出之前的ebp值(可以這么做是因為這個值在函數調用前一步被壓入堆棧)。這樣,ebp和esp就都恢復了調用函數B前的位置,也就是棧恢復函數B調用前的狀態。
這個過程在AT&T匯編中通過兩條指令完成,即:
此部分內容參考:http://blog.csdn.net/zsy2020314/article/details/9429707
__cdecl的特點
__cdecl 是 C Declaration 的縮寫,表示 C 和 C++ 默認的函數調用約定。是C/C++和MFCX的默認調用約定。
- 按從右至左的順序壓參數入棧、。
- 由調用者把參數彈出棧。切記:對于傳送參數的內存棧是由調用者來維護的,返回值在EAX中。因此對于像printf這樣可變參數的函數必須用這種約定。
- 編譯器在編譯的時候對這種調用規則的函數生成修飾名的時候,在輸出函數名前加上一個下劃線前綴,格式為_function。如函數int add(int a, int b)的修飾名是_add。
(1).為了驗證參數是從右至左的順序壓棧的,我們可以看下面這段代碼,Debug進行單步調試,可以看到我們的調用棧會先進入GetC(),再進入GetB(),最后進入GetA()。
(2).第二點“調用者把參數彈出棧”,這是編譯器的工作,暫時沒辦法驗證。要深入了解這部分,需要學習匯編語言相關的知識。
(3).函數的修飾名,這個可以通過對編譯出的dll使用VS的”dumpbin /exports ProjectName.dll”命令進行查看(后面章節會進行詳細介紹),或直接打開.obj文件查找對應的方法名(如搜索add)。
從代碼和程序調試的層面考慮,參數的壓棧順序和棧的清理我們都不用太觀注,因為這是編譯器的決定的,我們改變不了。但第三點卻常常困擾我們,因為如果不弄清楚這點,在多個庫之間(如dll、lib、exe)相互調用、依賴時常常出出現莫名其妙的錯誤。這個我在后面章節會進行詳細介紹。
__stdcall的特點
__stdcall是Standard Call的縮寫,是C++的標準調用方式,當然這是微軟定義的標準,__stdcall通常用于Win32 API中(可查看WINAPI的定義)。
- 按從右至左的順序壓參數入棧。
- 由被調用者把參數彈出棧。切記:函數自己在退出時清空堆棧,返回值在EAX中。
- __stdcall調用約定在輸出函數名前加上一個下劃線前綴,后面加上一個“@”符號和其參數的字節數,格式為_function@number。如函數int sub(int a, int b)的修飾名是_sub@8。
__fastcall的特點
__fastcall調用的主要特點就是快,因為它是通過寄存器來傳送參數的。
- 實際上__fastcall用ECX和EDX傳送前兩個DWORD或更小的參數,剩下的參數仍自右向左壓棧傳送,被調用的函數在返回前清理傳送參數的內存棧。
- __fastcall調用約定在輸出函數名前加上一個“@”符號,后面也是一個“@”符號和其參數的字節數,格式為@function@number,如double multi(double a, double b)的修飾名是@multi@16。
- __fastcall和__stdcall很象,唯一差別就是頭兩個參數通過寄存器傳送。注意通過寄存器傳送的兩個參數是從左向右的,即第1個參數進ECX,第2個進EDX,其他參數是從右向左的入棧,返回仍然通過EAX。
以上內容參考:http://www.3scard.com/index.php?m=blog&f=view&id=10
__thiscall
__thiscall是C++類成員函數缺省的調用約定,但它沒有顯示的聲明形式。因為在C++類中,成員函數調用還有一個this指針參數,因此必須特殊處理,thiscall調用約定的特點:
- 參數入棧:參數從右向左入棧
- this指針入棧:如果參數個數確定,this指針通過ecx傳遞給被調用者;如果參數個數不確定,this指針在所有參數壓棧后被壓入棧。
- 棧恢復:對參數個數不定的,調用者清理棧,否則函數自己清理棧。
總結
這里主要總結一下_cdecl、_stdcall、__fastcall三者之間的區別:
| 參數傳遞方式 | 右->左 | 右->左 | 左邊開始的兩個不大于4字節(DWORD)的參數分別放在ECX和EDX寄存器,其余的參數自右向左壓棧傳送 |
| 清理棧方 | 調用者清理 | 被調用函數清理 | 被調用函數清理 |
| 適用場合 | C/C++、MFC的默認方式; 可變參數的時候使用; | Win API | 要求速度快 |
| C編譯修飾約定 | _functionname | _functionname@number | @functionname@number |
本文章轉自:luoweifu
帶你玩轉Visual Studio——綁定進程調試
*
(function?()?{('pre.prettyprint code').each(function () {
var lines = (this).text().split(′\n′).length;varnumbering = $('
(this).addClass(′has?numbering′).parent().append(numbering);
for (i = 1; i <= lines; i++) {
numbering.append(('
- ').text(i));
};
$numbering.fadeIn(1700);
});
});
歡迎使用Markdown編輯器寫博客
本Markdown編輯器使用StackEdit修改而來,用它寫博客,將會帶來全新的體驗哦:
- Markdown和擴展Markdown簡潔的語法
- 代碼塊高亮
- 圖片鏈接和圖片上傳
- LaTex數學公式
- UML序列圖和流程圖
- 離線寫博客
- 導入導出Markdown文件
- 豐富的快捷鍵
快捷鍵
- 加粗 Ctrl + B
- 斜體 Ctrl + I
- 引用 Ctrl + Q
- 插入鏈接 Ctrl + L
- 插入代碼 Ctrl + K
- 插入圖片 Ctrl + G
- 提升標題 Ctrl + H
- 有序列表 Ctrl + O
- 無序列表 Ctrl + U
- 橫線 Ctrl + R
- 撤銷 Ctrl + Z
- 重做 Ctrl + Y
Markdown及擴展
Markdown 是一種輕量級標記語言,它允許人們使用易讀易寫的純文本格式編寫文檔,然后轉換成格式豐富的HTML頁面。 —— [ 維基百科 ]
使用簡單的符號標識不同的標題,將某些文字標記為粗體或者斜體,創建一個鏈接等,詳細語法參考幫助?。
本編輯器支持 Markdown Extra , 擴展了很多好用的功能。具體請參考Github.
表格
Markdown Extra 表格語法:
項目價格 Computer $1600 Phone $12 Pipe $1 可以使用冒號來定義對齊方式:
項目價格數量 Computer 1600 元 5 Phone 12 元 12 Pipe 1 元 234 定義列表
Markdown Extra 定義列表語法:項目1項目2- 定義 A
- 定義 B
項目3- 定義 C
定義 D
定義D內容
代碼塊
代碼塊語法遵循標準markdown代碼,例如:
@requires_authorization def somefunc(param1='', param2=0):'''A docstring'''if param1 > param2: # interestingprint 'Greater'return (param2 - param1 + 1) or None class SomeClass:pass >>> message = '''interpreter ... prompt'''腳注
生成一個腳注1.
目錄
用 [TOC]來生成目錄:
- 什么是調用約定
- 聲明和定義處調用約定必須要相同
- 函數的調用過程
- __cdecl的特點
- __stdcall的特點
- __fastcall的特點
- __thiscall
- 總結
- 快捷鍵
- Markdown及擴展
- 表格
- 定義列表
- 代碼塊
- 腳注
- 目錄
- 數學公式
- UML 圖
- 離線寫博客
- 瀏覽器兼容
數學公式
使用MathJax渲染LaTex 數學公式,詳見math.stackexchange.com.
- 行內公式,數學公式為:Γ(n)=(n?1)!?n∈N。
- 塊級公式:
x=?b±b2?4ac???????√2a
更多LaTex語法請參考 這兒.
UML 圖:
可以渲染序列圖:
Created with Rapha?l 2.1.0張三張三李四李四嘿,小四兒, 寫博客了沒?李四愣了一下,說:忙得吐血,哪有時間寫。或者流程圖:
Created with Rapha?l 2.1.0開始我的操作確認?結束yesno- 關于 序列圖 語法,參考 這兒,
- 關于 流程圖 語法,參考 這兒.
離線寫博客
即使用戶在沒有網絡的情況下,也可以通過本編輯器離線寫博客(直接在曾經使用過的瀏覽器中輸入write.blog.csdn.net/mdeditor即可。Markdown編輯器使用瀏覽器離線存儲將內容保存在本地。
用戶寫博客的過程中,內容實時保存在瀏覽器緩存中,在用戶關閉瀏覽器或者其它異常情況下,內容不會丟失。用戶再次打開瀏覽器時,會顯示上次用戶正在編輯的沒有發表的內容。
博客發表后,本地緩存將被刪除。
用戶可以選擇 把正在寫的博客保存到服務器草稿箱,即使換瀏覽器或者清除緩存,內容也不會丟失。
注意:雖然瀏覽器存儲大部分時候都比較可靠,但為了您的數據安全,在聯網后,請務必及時發表或者保存到服務器草稿箱。
瀏覽器兼容
- 目前,本編輯器對Chrome瀏覽器支持最為完整。建議大家使用較新版本的Chrome。
- IE9以下不支持
- IE9,10,11存在以下問題
- 不支持離線功能
- IE9不支持文件導入導出
- IE10不支持拖拽文件導入
- 這里是 腳注 的 內容. ?
總結
- 上一篇: 【Unity3D插件】PUN 2插件分享
- 下一篇: C++动态绑定及返回类型协变