簡體   English   中英

在C ++中,可變參數函數(在參數列表末尾帶有...的函數)必須遵循__cdecl調用約定嗎?

[英]In C++, do variadic functions (those with … at the end of the parameter list) necessarily follow the __cdecl calling convention?

我知道__stdcall函數不能有省略號,但我想確保沒有支持stdarg.h函數的平台來調用除__cdecl或__stdcall之外的約定。

調用約定必須是調用者從堆棧中清除參數的約束(因為被調用者不知道將傳遞什么)。

但這並不一定與微軟稱之為“__cdecl”的內容相對應。 例如,在SPARC上,它通常會傳遞寄存器中的參數,因為這就是SPARC設計工作的方式 - 它的寄存器基本上充當調用堆棧,如果調用足夠深,它會溢出到主存儲器他們將不再適合登記。

雖然我對它不太確定,但我期望在IA64(Itanium)上大致相同 - 它也有一個巨大的寄存器集(如果內存服務則需要幾百個)。 如果我沒有弄錯的話,它對你如何使用寄存器更加寬容,但我希望它至少在很多時候都能被類似地使用。

為什么這對你很重要? 使用stdarg.h及其宏的目的是隱藏調用約定與代碼之間的差異,因此它可以移植地使用變量參數。

編輯,基於評論:好的,現在我明白你在做什么(至少足以改善答案)。 鑒於您已經(顯然)擁有處理默認ABI變體的代碼,事情就更簡單了。 這只留下了可變函數是否總是使用“默認ABI”的問題,無論發生在手頭平台上的是什么。 以“stdcall”和“default”為唯一選項,我認為答案是肯定的。 例如,在Windows上, wsprintfwprintf打破了經驗法則,並使用cdecl調用約定而不是stdcall。

您可以確定的最明確的方法是分析調用約定。 要使可變函數起作用,您的調用約定需要幾個屬性:

  • 被調用者必須能夠從堆棧頂部的固定偏移量訪問不屬於變量參數列表的參數。 這要求編譯器將參數從右向左推入堆棧。 (這包括諸如printf的第一個參數,格式規范之類的東西。此外,變量參數列表本身的地址也必須從已知位置派生。)
  • 一旦函數返回,調用者必須負責從堆棧中刪除參數,因為只有編譯器在為調用者生成代碼時才知道首先將多少參數壓入堆棧。 可變函數本身沒有這些信息。

stdcall將無法工作,因為被調用者負責從堆棧中彈出參數。 在舊的16位Windows時代, pascal無法工作,因為它從左到右將參數壓入堆棧。

當然,正如其他答案所暗示的那樣,許多平台在調用約定方面沒有給你任何選擇,使得這個問題與那些問題無關。

請考慮x86系統上的以下功能:

void __stdcall something(char *,...);

該函數將自身聲明為__stdcall,這是一個callee-clean約定。 但是變量函數不能被callee-clean,因為被調用者不知道傳遞了多少參數,所以它不知道應該清理多少個參數。

Microsoft Visual Studio C / C ++編譯器通過將調用約定靜默轉換為__cdecl來解決此沖突,__ cdecl是唯一受支持的可變參數調用約定,用於不接受隱藏此參數的函數。

為什么這種轉換是以靜默方式進行而不是產生警告或錯誤?

我的猜測是,將編譯器選項/ Gr(將默認調用約定設置為__fastcall)和/ Gz(將默認調用約定設置為__stdcall)設置為不那么煩人。

將可變參數函數自動轉換為__cdecl意味着您只需將/ Gr或/ Gz命令行開關添加到編譯器選項中,所有內容仍然可以編譯和運行(只需使用新的調用約定)。

另一種看待這種情況的方法不是將編譯器視為將variadic __stdcall轉換為__cdecl,而是簡單地說“對於可變參數函數,__ stdcall是調用者干凈的”。

點擊這里

你的意思是'MSVC支持的平台'還是一般規則?即使你只限於MSVC支持的平台,你仍然有像IA64AMD64這樣的情況,其中只有“一個”調用約定,而調用約定是叫__stdcall ,但它肯定與x86上的__stdcall不一樣。

AFAIK,調用約定的多樣性對於x86上的DOS / Windows是唯一的。 大多數其他平台都有編譯器附帶操作系統並標准化約定。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM