C語言的變長參數
C語言的變長參數原理
引言: 在看《程序員的自我修養:第十一章運行庫-11.2.2C語言標準庫》時講到C語言的變長參數以及cdecl,
所以搜集一些資料幫助理解。
https://zh.wikipedia.org ? zh-hans
關于調用約定(cdecl、fastcall、stcall、thiscall) 的一點知識- 風 ...
https://www.laruence.com ? 2008/.../...
c語言如何判斷一個聲明到底是函數還是指針還是數組 ... - 知乎
https://www.zhihu.com ? question
變長參數函數:
Int printf(const char* format, …);
cdecl:
一種X86的調用約定,和語言沒有關系。變長參數實現的基礎是cdecl。
cdecl(C declaration,即C聲明)是源起C語言的一種調用約定,也是C語言的事實上的標準。在x86架構上,其內容包括:
- 函數實參在線程棧上按照從右至左的順序依次壓棧。
- 函數結果保存在寄存器EAX/AX/AL中
- 浮點型結果存放在寄存器ST0中
- 編譯后的函數名前綴以一個下劃線字符
- 調用者負責從線程棧中彈出實參(即清棧)
- 8比特或者16比特長的整形實參提升為32比特長。
- 受到函數調用影響的寄存器(volatile registers):EAX, ECX, EDX, ST0 - ST7, ES, GS
- 不受函數調用影響的寄存器: EBX, EBP, ESP, EDI, ESI, CS, DS
- RET指令從函數被調用者返回到調用者(實質上是讀取寄存器EBP所指的線程棧之處保存的函數返回地址并加載到IP寄存器)
事實上C++語言默認也是cdecl, 下面的匯編可以清晰的看到調用者完成了清理棧操作


補充stdcall
stdcall是由微軟創建的調用約定,是Windows API的標準調用約定。非微軟的編譯器并不總是支持該調用協議。GCC編譯器如下使用:
int __attribute__((__stdcall__ )) func()
stdcall是Pascal調用約定與cdecl調用約定的折衷:被調用者負責清理線程棧,參數從右往左入棧。其他各方面基本與cdecl相同。但是編譯后的函數名后綴以符號"@",后跟傳遞的函數參數所占的棧空間的字節長度。寄存器EAX, ECX和EDX被指定在函數中使用,返回值放置在EAX中。stdcall對于微軟Win32 API和Open Watcom C++是標準。
微軟的編譯工具規定:PASCAL, WINAPI, APIENTRY, FORTRAN, CALLBACK, STDCALL, __far __pascal, __fortran, __stdcall均是指此種調用約定。
也就是說調用windows api的代碼的匯編,調用者不清理參數棧,由api函數的匯編代碼清理棧。
變長參數部分待補充
uint8_t spdlogInit(const char *section);
void logerror(const char *__restrict fmt, ...);
#define SPDLOGC_ERROR(fmt, ...) logerror(fmt, ##__VA_ARGS__)
uint8_t spdlogInit(const char *section)
{
return util::spdlogInit(section);
}
void logerror(const char *__restrict fmt, ...)
{
va_list args;
va_start(args, fmt);
char s[1024];
vsnprintf(s, 1024, fmt, args);
SPDLOG_ERROR(s);
va_end(args);
}
有意思的技巧:
來源:https://www.zhihu.com/question/439224121
Int *foo[3]
看見[N] 讀作 an array of N
看見() 讀作 a function that returns
看見T* 讀作 a point to T
然后從變量開始,先讀右邊的東西,再讀左邊的東西,然后被括號包裹著的話,就跳出去重復這個動作。
比如int *foo[3]
An array of 3 points to int

浙公網安備 33010602011771號