c/c++ 自己设计不定参处理方式,代替va_list
函数调用约定
什么是函数调用约定?我们平常的函数是什么调用约定?不同的调用约定又有什么区别?
以VS2019为例新建的项目在不设置调用约定的情况下的调用约定默认是 __cdecl
WindowsAPI 一般是 __stdcall
类的成员函数 一般是 __thiscall
x64程序默认是 __fastcall
不同调用约定的区别
调用约定 | 传参方式 | 栈平衡 |
---|---|---|
__cdecl | 自右向左依次压入栈中 | 由调用者恢复栈 |
__stdcall | 自右向左依次压入栈中 | 由函数本身(被调用者)恢复栈 |
__fastcall | x32/x64分别 ECX/RCX,EDX/RDX,R8,R9传递前2/4个参数,其余参数自右向左依次压入栈中 | 由函数本身(被调用者)恢复栈 |
__thiscall | 由ECX/RCX传递this指针 传参方式按照默认 | — |
由上可知 只有__cdecl才可以做不定参函数,因为只有__cdecl是由调用者恢复栈,在VS中给函数加上__stdcall为啥还能定义不定参,是不是我在胡扯呢…那是因为使用了不定参后不管你定义的什么调用约定都会以__cdecl约定编译,如图
自己设计不定参的处理方式
既然不定参函数都是__cdecl约定,__cdecl的参数都是在栈上的
而且是从右到左依次压栈,那么我们只要知道栈顶的地址(也就是第一个参数的地址)
就可以获取到其它参数
1.知道第一个参数名
//求平均数函数
intptr_t ave(intptr_t count, ...)
{
intptr_t* arg = &count; //这里获取到第一个参数的地址(栈顶)
//arg[0] 就是第一个参数 arg[1] 就是第二个参数
intptr_t sum{};
for (size_t i = 1; i <= arg[0]; i++)
{
sum += arg[i];
}
return sum / arg[0];
}
int main()
{
std::cout << ave(3, 1, 2, 3);
//执行结果 2
}
2.不知道第一个参数名
我们知道局部变量其实也是栈上的空间…他喵的这编译出来的局部变量没有按照哪个先声明,哪个先压栈,而是函数头直接一起压栈,然后ebp - 4,ebp - 8来操作…而且也没有按照声明顺序…可能是编译器飘了吧…
那就只能用汇编把第一个参数地址拿出来了…
intptr_t ave2(...)
{
intptr_t* arg;
__asm mov arg,ebp
intptr_t sum{};
arg += 2;//arg[0] = 函数头的push ebp ,arg[1] 是 call ave2压入的反回地址 arg[2]才是第一个参数参数
for (size_t i = 1; i <= arg[0]; i++)
{
sum += arg[i];
}
return sum / arg[0];
}
int main()
{
std::cout << ave2(7, 1, 2, 3, 4, 5, 6, 7) << std::endl;
//输出结果4
}
结束
第一次写文章…不好勿喷…如果有错的地方请指出来…