user-0 提问: __stdcall与变参函数的栈清理

回复:
user: user-0
content: 引用 4 楼 Intel0011 的回复:看了一下gcc的汇编代码，与VC是相似的，没什么不同，没明白你说的意思 关于那个__cdecl函数指针调用__stdcall却正常的问题，我搞懂了，和你说下我的心得吧  在gcc上测试 之前__cdecl指针能调用__stdcall函数应该只是个巧合，函数自身的栈空间足够大，调用__stdcall函数的次数少，所以没出岔子，调用次数一多就出问题了，因为esp一直是在加的  __stdcall指针调用__cdecl函数却是可行的，因为调用函数后不修改esp，esp就是在减的，相当于增加了main的栈空间。而且函数变量gcc通过ebp定位，而ebp在传参之后、调用函数之后入栈，最后可以准确还原，而传参通过esp定位，esp又是在减的，也就是传参的入栈位置都是在新的栈空间。  然后是cl的测试 __cdecl调用__stdcall一下就失败了，记得之前调用WinAPI是测试成功了的，忘了是什么情况了  不过它的__stdcall调用__cdecl也成功了，原因应该跟gcc的差不多，传参通过push，那么就是以esp定位，但不同的是，它变量是直接写的变量名，如move dword ptr[c] , 0 ，我估计本质应该也是用的ebp定位

user: user-1
content: 1. 变参函数用__stdcall 是无法实现的, 所以编译器强制编译成了__cdecl 2. 函数参数进栈一般用push, 但是也可以直接 mov [esp+x], vvv 的方式, 作用是等效的

user: user-2
content: 引用 3 楼 六道佩恩 的回复:话说调用约定会影响变参函数的参数的类型提升吗？  LoadLibrary+__cdecl函数指针调用Windows API，按理说会出问题的对吧，怪就怪在GCC和CL在命令行的编译结果运行正常，而VC的IDE上（CPP）就出问题了，得__stdcall函数指针。这波没出问题反而搞不懂，__cdecl和__stdcall都试了，汇编是不同的（调用后修改esp与否），但运行都正常？？？Windows API使用__stdcall的话，最后应该是修改了栈顶的吧？  还是这个__cdecl和__stdcall，在gcc上，__cdecl，调用函数后没有调整栈顶；__stdcall，调用后反而调整栈顶了，函数最后修改了栈顶，结果调用结束后gcc又改了回来，使栈顶还是指向调用各个函数时通用的传参空间。我是不是可以认为，表面上看是gcc对__cdecl没有修改栈顶而对__stdcall修改了，是由于它对栈的使用方式和VC不同，所以才造成了表象相反，在它的使用方式内，__cdecl没有改esp仍视为清理了栈，而__stdcall改了esp应视为没有清理栈，是吧？ 函数的参数的类型提升是在C语言标准中已经规定了的，与调用约定的类型无关，调用约定只是规定了参数的压栈顺序以及由谁来做栈平衡的动作 Windows API大部分是__stdcall，只有变参函数是__cdecl，因为Windows API是已经编译好的，其调用约定已定  看了一下gcc的汇编代码，与VC是相似的，没什么不同，没明白你说的意思  int __attribute__((__stdcall__)) fun1(int num) {     return num + num; }  fun1:         push    ebp         mov     ebp, esp         mov     eax, DWORD PTR [ebp+8]         add     eax, eax         pop     ebp         ret     4  //这里有清栈，由于是__stdcall，所以由自己清栈  int __attribute__((__cdecl__)) fun2(int i, int j) {     return i + j; }  fun2:         push    ebp         mov     ebp, esp         mov     edx, DWORD PTR [ebp+8]         mov     eax, DWORD PTR [ebp+12]         add     eax, edx         pop     ebp         ret   //这里没有清栈，由于是__cdecl，所以由调用方清栈  int main(void) {     fun1(1);     fun2(1, 2);     return 0; }  main:         push    ebp         mov     ebp, esp         push    1 //传参的栈空间         call    fun1 //fun1自己清栈         push    2 //传参的栈空间         push    1 //传参的栈空间         call    fun2         add     esp, 8 //调用方清栈         mov     eax, 0         leave         ret

user: user-0
content: 引用 4 楼 Intel0011 的回复:Windows API大部分是__stdcall，只有变参函数是__cdecl，因为Windows API是已经编译好的，其调用约定已定  我的意思是，那些函数已经确定，__stdcall导致末尾的ret会修改栈顶对吧？ 那么调用方也需要按__stdcall来调用对吧？ 但问题在于，我测试调用方按__cdecl来调用，结果也能正常运行？  为了确定gcc确实是按__cdecl编译的，对比__stdcall和__cdecl调用函数后的操作，一个没动作，一个有修改esp的操作，所以它确实按我指示的__cdecl调用了__stdcall函数，但却正常运行了（不是一个函数，是多个）

user: user-0
content: 引用 2 楼 Intel0011 的回复:1、对于变参函数，其调用约定只能为__cdecl，因为只有调用者才知道传了多少个参数，被调用者是无法知道的（有标志意义的参数除外），所以这时也只能由调用者来做栈清理，恢复到调用前的状态 2、每次传参都是拿同一段空间反复使用，这本身就是栈的处理方式，是优于堆的地方，每一次函数调用后，都会恢复到调用前的状态，所以一般情况下栈不需要特别大的空间，函数式编程中的尾递归就是栈的极致利用方式 3、不论是那种调用约定，只要每一次函数调用后将栈恢复到调用前的状态，都可以认为是栈清理，也叫栈平衡   话说调用约定会影响变参函数的参数的类型提升吗？  LoadLibrary+__cdecl函数指针调用Windows API，按理说会出问题的对吧，怪就怪在GCC和CL在命令行的编译结果运行正常，而VC的IDE上（CPP）就出问题了，得__stdcall函数指针。这波没出问题反而搞不懂，__cdecl和__stdcall都试了，汇编是不同的（调用后修改esp与否），但运行都正常？？？Windows API使用__stdcall的话，最后应该是修改了栈顶的吧？  还是这个__cdecl和__stdcall，在gcc上，__cdecl，调用函数后没有调整栈顶；__stdcall，调用后反而调整栈顶了，函数最后修改了栈顶，结果调用结束后gcc又改了回来，使栈顶还是指向调用各个函数时通用的传参空间。我是不是可以认为，表面上看是gcc对__cdecl没有修改栈顶而对__stdcall修改了，是由于它对栈的使用方式和VC不同，所以才造成了表象相反，在它的使用方式内，__cdecl没有改esp仍视为清理了栈，而__stdcall改了esp应视为没有清理栈，是吧？

user: user-2
content: 引用 楼主 六道佩恩 的回复:我主要有两个问题： 1. 关于变参函数，是不是不管声明何种调用约定，其都会实现为__cdecl？下面是VC的汇编，实际指定的是__stdcall。 2. gcc的对栈清理的方式怎么和VC不一样啊？或者说这算栈清理吗？ 观察了它几次调用，发现它每次传参都是拿同一段空间反复使用，就是图中入栈的那段，对不同函数的调用，传参都是用的这同一段，作为调用方把控了传参的栈空间，是不是也相当于做了栈清理？或者说这能算栈清理吗？ （编译用的32位，非x64约定） 1、对于变参函数，其调用约定只能为__cdecl，因为只有调用者才知道传了多少个参数，被调用者是无法知道的（有标志意义的参数除外），所以这时也只能由调用者来做栈清理，恢复到调用前的状态 2、每次传参都是拿同一段空间反复使用，这本身就是栈的处理方式，是优于堆的地方，每一次函数调用后，都会恢复到调用前的状态，所以一般情况下栈不需要特别大的空间，函数式编程中的尾递归就是栈的极致利用方式 3、不论是那种调用约定，只要每一次函数调用后将栈恢复到调用前的状态，都可以认为是栈清理，也叫栈平衡

user: user-3
content: 1. stdcall 等调用约定的规则就限制了他们进行变参函数的使用   。    当然也有些特殊情况。 2.栈清理  是什么东西??    参数传递的入栈和出栈吗？   gcc 和 vc 传参时对栈的使用基本是相同的   ，只不过 gcc 提前分配了空间  而vc使用push ，但是每次使用完栈都是平衡的 。