日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

C++调用约定

發(fā)布時間:2023/12/20 c/c++ 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C++调用约定 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

<div class="markdown_views"><p>有一定C++開發(fā)經(jīng)驗的人一定對”__cdecl、__stdcall、__fastcall”肯定不陌生吧!但你真正理解了嗎?是的,我曾在這采了無數(shù)個坑,栽了無數(shù)個跟頭,終于忍無可忍要把它總結(jié)一下(雖然我已經(jīng)有能力解決大部分這種問題了)!</p>

什么是調(diào)用約定

函數(shù)的調(diào)用約定,顧名思義就是對函數(shù)調(diào)用的一個約束和規(guī)定(規(guī)范),描述了函數(shù)參數(shù)是怎么傳遞和由誰清除堆棧的。它決定以下內(nèi)容:(1)函數(shù)參數(shù)的壓棧順序,(2)由調(diào)用者還是被調(diào)用者把參數(shù)彈出棧,(3)以及產(chǎn)生函數(shù)修飾名的方法。

我們知道函數(shù)由以下幾部分構(gòu)成:返回值類型 函數(shù)名(參數(shù)列表),如:
【code1】

void function(); int add(int a, int b);

以上是大家所熟知的構(gòu)成部分,其實函數(shù)的構(gòu)成還有一部分,那就是調(diào)用約定。如下:
【code2】

void __cdecl function(); int __stdcall add(int a, int b);

上面的__cdecl和__stdcall就是調(diào)用約定,其中__cdecl是C和C++默認的調(diào)用約定,所以通常我們的代碼都如 【code1】中那樣定義,編譯器默認會為我們使用__cdecl調(diào)用約定。常見的調(diào)用約定有__cdecl、__stdcall、fastcall,應(yīng)用最廣泛的是__cdecl和__stdcall,下面我們會詳細進行講述。。還有一些不常見的,如 __pascal、__thiscall、__vectorcall。

聲明和定義處調(diào)用約定必須要相同

在VC++中,調(diào)用約定是函數(shù)類型的一部分,因此函數(shù)的聲明和定義處調(diào)用約定要相同,不能只在聲明處有調(diào)用約定,而定義處沒有或與聲明不同。如下:
【code3】 錯誤的使用一:

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

報錯:

error C2373: ‘a(chǎn)dd’: 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: ‘a(chǎn)dd’: 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: ‘a(chǎn)dd’: 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; }

函數(shù)的調(diào)用過程

要深入理解函數(shù)調(diào)用約定,你須要了解函數(shù)的調(diào)用過程和調(diào)用細節(jié)。
假設(shè)函數(shù)A調(diào)用函數(shù)B,我們稱A函數(shù)為”調(diào)用者”,B函數(shù)為“被調(diào)用者”。如下面的代碼,ShowResult為調(diào)用者,add為被調(diào)用者。

int add(int a, int b) {return a + b; }void ShowResult() {std::cout << add(5, 10) << std::endl; }

函數(shù)調(diào)用過程可以這么描述:
(1)先將調(diào)用者(A)的堆棧的基址(ebp)入棧,以保存之前任務(wù)的信息。
(2)然后將調(diào)用者(A)的棧頂指針(esp)的值賦給ebp,作為新的基址(即被調(diào)用者B的棧底)。
(3)然后在這個基址(被調(diào)用者B的棧底)上開辟(一般用sub指令)相應(yīng)的空間用作被調(diào)用者B的棧空間。
(4)函數(shù)B返回后,從當(dāng)前棧幀的ebp即恢復(fù)為調(diào)用者A的棧頂(esp),使棧頂恢復(fù)函數(shù)B被調(diào)用前的位置;然后調(diào)用者A再從恢復(fù)后的棧頂可彈出之前的ebp值(可以這么做是因為這個值在函數(shù)調(diào)用前一步被壓入堆棧)。這樣,ebp和esp就都恢復(fù)了調(diào)用函數(shù)B前的位置,也就是棧恢復(fù)函數(shù)B調(diào)用前的狀態(tài)。
這個過程在AT&T匯編中通過兩條指令完成,即:

leaveret這兩條指令更直白點就相當(dāng)于:mov %ebp , %esppop %ebp

此部分內(nèi)容參考:http://blog.csdn.net/zsy2020314/article/details/9429707

__cdecl的特點

__cdecl 是 C Declaration 的縮寫,表示 C 和 C++ 默認的函數(shù)調(diào)用約定。是C/C++和MFCX的默認調(diào)用約定。

  • 按從右至左的順序壓參數(shù)入棧、。
  • 由調(diào)用者把參數(shù)彈出棧。切記:對于傳送參數(shù)的內(nèi)存棧是由調(diào)用者來維護的,返回值在EAX中。因此對于像printf這樣可變參數(shù)的函數(shù)必須用這種約定。
  • 編譯器在編譯的時候?qū)@種調(diào)用規(guī)則的函數(shù)生成修飾名的時候,在輸出函數(shù)名前加上一個下劃線前綴,格式為_function。如函數(shù)int add(int a, int b)的修飾名是_add。

(1).為了驗證參數(shù)是從右至左的順序壓棧的,我們可以看下面這段代碼,Debug進行單步調(diào)試,可以看到我們的調(diào)用棧會先進入GetC(),再進入GetB(),最后進入GetA()。

(2).第二點“調(diào)用者把參數(shù)彈出棧”,這是編譯器的工作,暫時沒辦法驗證。要深入了解這部分,需要學(xué)習(xí)匯編語言相關(guān)的知識。

(3).函數(shù)的修飾名,這個可以通過對編譯出的dll使用VS的”dumpbin /exports ProjectName.dll”命令進行查看(后面章節(jié)會進行詳細介紹),或直接打開.obj文件查找對應(yīng)的方法名(如搜索add)。

從代碼和程序調(diào)試的層面考慮,參數(shù)的壓棧順序和棧的清理我們都不用太觀注,因為這是編譯器的決定的,我們改變不了。但第三點卻常常困擾我們,因為如果不弄清楚這點,在多個庫之間(如dll、lib、exe)相互調(diào)用、依賴時常常出出現(xiàn)莫名其妙的錯誤。這個我在后面章節(jié)會進行詳細介紹。

__stdcall的特點

__stdcall是Standard Call的縮寫,是C++的標(biāo)準(zhǔn)調(diào)用方式,當(dāng)然這是微軟定義的標(biāo)準(zhǔn),__stdcall通常用于Win32 API中(可查看WINAPI的定義)。

  • 按從右至左的順序壓參數(shù)入棧。
  • 由被調(diào)用者把參數(shù)彈出棧。切記:函數(shù)自己在退出時清空堆棧,返回值在EAX中。
  • __stdcall調(diào)用約定在輸出函數(shù)名前加上一個下劃線前綴,后面加上一個“@”符號和其參數(shù)的字節(jié)數(shù),格式為_function@number。如函數(shù)int sub(int a, int b)的修飾名是_sub@8。

__fastcall的特點

__fastcall調(diào)用的主要特點就是快,因為它是通過寄存器來傳送參數(shù)的。

  • 實際上__fastcall用ECX和EDX傳送前兩個DWORD或更小的參數(shù),剩下的參數(shù)仍自右向左壓棧傳送,被調(diào)用的函數(shù)在返回前清理傳送參數(shù)的內(nèi)存棧。
  • __fastcall調(diào)用約定在輸出函數(shù)名前加上一個“@”符號,后面也是一個“@”符號和其參數(shù)的字節(jié)數(shù),格式為@function@number,如double multi(double a, double b)的修飾名是@multi@16。
  • __fastcall和__stdcall很象,唯一差別就是頭兩個參數(shù)通過寄存器傳送。注意通過寄存器傳送的兩個參數(shù)是從左向右的,即第1個參數(shù)進ECX,第2個進EDX,其他參數(shù)是從右向左的入棧,返回仍然通過EAX。

以上內(nèi)容參考:http://www.3scard.com/index.php?m=blog&f=view&id=10

__thiscall

__thiscall是C++類成員函數(shù)缺省的調(diào)用約定,但它沒有顯示的聲明形式。因為在C++類中,成員函數(shù)調(diào)用還有一個this指針參數(shù),因此必須特殊處理,thiscall調(diào)用約定的特點:

  • 參數(shù)入棧:參數(shù)從右向左入棧
  • this指針入棧:如果參數(shù)個數(shù)確定,this指針通過ecx傳遞給被調(diào)用者;如果參數(shù)個數(shù)不確定,this指針在所有參數(shù)壓棧后被壓入棧。
  • 棧恢復(fù):對參數(shù)個數(shù)不定的,調(diào)用者清理棧,否則函數(shù)自己清理棧。

總結(jié)

這里主要總結(jié)一下_cdecl、_stdcall、__fastcall三者之間的區(qū)別:

要點__cdecl__stdcall__fastcall
參數(shù)傳遞方式右->左右->左左邊開始的兩個不大于4字節(jié)(DWORD)的參數(shù)分別放在ECX和EDX寄存器,其余的參數(shù)自右向左壓棧傳送
清理棧方調(diào)用者清理被調(diào)用函數(shù)清理被調(diào)用函數(shù)清理
適用場合C/C++、MFC的默認方式; 可變參數(shù)的時候使用;Win API要求速度快
C編譯修飾約定_functionname_functionname@number@functionname@number

本文章轉(zhuǎn)自:luoweifu
帶你玩轉(zhuǎn)Visual Studio——綁定進程調(diào)試

*



(function?()?{('pre.prettyprint code').each(function () {
var lines = (this).text().split(\n).length;varnumbering = $('
    ').addClass('pre-numbering').hide();
    (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數(shù)學(xué)公式
      • UML序列圖和流程圖
      • 離線寫博客
      • 導(dǎo)入導(dǎo)出Markdown文件
      • 豐富的快捷鍵

      快捷鍵

      • 加粗 Ctrl + B
      • 斜體 Ctrl + I
      • 引用 Ctrl + Q
      • 插入鏈接 Ctrl + L
      • 插入代碼 Ctrl + K
      • 插入圖片 Ctrl + G
      • 提升標(biāo)題 Ctrl + H
      • 有序列表 Ctrl + O
      • 無序列表 Ctrl + U
      • 橫線 Ctrl + R
      • 撤銷 Ctrl + Z
      • 重做 Ctrl + Y

      Markdown及擴展

      Markdown 是一種輕量級標(biāo)記語言,它允許人們使用易讀易寫的純文本格式編寫文檔,然后轉(zhuǎn)換成格式豐富的HTML頁面。 —— [ 維基百科 ]

      使用簡單的符號標(biāo)識不同的標(biāo)題,將某些文字標(biāo)記為粗體或者斜體,創(chuàng)建一個鏈接等,詳細語法參考幫助?。

      本編輯器支持 Markdown Extra ,  擴展了很多好用的功能。具體請參考Github.

      表格

      Markdown Extra 表格語法:

      項目價格
      Computer$1600
      Phone$12
      Pipe$1

      可以使用冒號來定義對齊方式:

      項目價格數(shù)量
      Computer1600 元5
      Phone12 元12
      Pipe1 元234

      定義列表

      Markdown Extra 定義列表語法:項目1項目2
      定義 A
      定義 B
      項目3
      定義 C

      定義 D

      定義D內(nèi)容

      代碼塊

      代碼塊語法遵循標(biāo)準(zhǔn)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]來生成目錄:

      • 什么是調(diào)用約定
        • 聲明和定義處調(diào)用約定必須要相同
        • 函數(shù)的調(diào)用過程
      • __cdecl的特點
      • __stdcall的特點
      • __fastcall的特點
      • __thiscall
      • 總結(jié)
        • 快捷鍵
        • Markdown及擴展
          • 表格
          • 定義列表
          • 代碼塊
          • 腳注
          • 目錄
          • 數(shù)學(xué)公式
          • UML 圖
        • 離線寫博客
        • 瀏覽器兼容

      數(shù)學(xué)公式

      使用MathJax渲染LaTex 數(shù)學(xué)公式,詳見math.stackexchange.com.

      • 行內(nèi)公式,數(shù)學(xué)公式為:Γ(n)=(n?1)!?nN
      • 塊級公式:

      x=?b±b2?4ac???????2a

      更多LaTex語法請參考 這兒.

      UML 圖:

      可以渲染序列圖:

      Created with Rapha?l 2.1.0張三張三李四李四嘿,小四兒, 寫博客了沒?李四愣了一下,說:忙得吐血,哪有時間寫。

      或者流程圖:

      Created with Rapha?l 2.1.0開始我的操作確認?結(jié)束yesno
      • 關(guān)于 序列圖 語法,參考 這兒,
      • 關(guān)于 流程圖 語法,參考 這兒.

      離線寫博客

      即使用戶在沒有網(wǎng)絡(luò)的情況下,也可以通過本編輯器離線寫博客(直接在曾經(jīng)使用過的瀏覽器中輸入write.blog.csdn.net/mdeditor即可。Markdown編輯器使用瀏覽器離線存儲將內(nèi)容保存在本地。

      用戶寫博客的過程中,內(nèi)容實時保存在瀏覽器緩存中,在用戶關(guān)閉瀏覽器或者其它異常情況下,內(nèi)容不會丟失。用戶再次打開瀏覽器時,會顯示上次用戶正在編輯的沒有發(fā)表的內(nèi)容。

      博客發(fā)表后,本地緩存將被刪除。 

      用戶可以選擇 把正在寫的博客保存到服務(wù)器草稿箱,即使換瀏覽器或者清除緩存,內(nèi)容也不會丟失。

      注意:雖然瀏覽器存儲大部分時候都比較可靠,但為了您的數(shù)據(jù)安全,在聯(lián)網(wǎng)后,請務(wù)必及時發(fā)表或者保存到服務(wù)器草稿箱

      瀏覽器兼容

    • 目前,本編輯器對Chrome瀏覽器支持最為完整。建議大家使用較新版本的Chrome。
    • IE9以下不支持
    • IE9,10,11存在以下問題
    • 不支持離線功能
    • IE9不支持文件導(dǎo)入導(dǎo)出
    • IE10不支持拖拽文件導(dǎo)入


    • 這里是 腳注 的 內(nèi)容. ?

    總結(jié)

    以上是生活随笔為你收集整理的C++调用约定的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。