__stdcall与变参函数的栈清理

六道佩恩 2021-05-14 06:21:34
我主要有两个问题:
1. 关于变参函数,是不是不管声明何种调用约定,其都会实现为__cdecl?下面是VC的汇编,实际指定的是__stdcall。


2. gcc的对栈清理的方式怎么和VC不一样啊?或者说这算栈清理吗?


观察了它几次调用,发现它每次传参都是拿同一段空间反复使用,就是图中入栈的那段,对不同函数的调用,传参都是用的这同一段,作为调用方把控了传参的栈空间,是不是也相当于做了栈清理?或者说这能算栈清理吗?

(编译用的32位,非x64约定)
...全文
367 7 打赏 收藏 转发到动态 举报
写回复
用AI写文章
7 条回复
切换为时间正序
请发表友善的回复…
发表回复
六道佩恩 2021-05-20
  • 打赏
  • 举报
回复
引用 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定位
Yofoo 2021-05-15
  • 打赏
  • 举报
回复
1. 变参函数用__stdcall 是无法实现的, 所以编译器强制编译成了__cdecl 2. 函数参数进栈一般用push, 但是也可以直接 mov [esp+x], vvv 的方式, 作用是等效的
Intel0011 2021-05-15
  • 打赏
  • 举报
回复
引用 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
六道佩恩 2021-05-15
  • 打赏
  • 举报
回复
引用 4 楼 Intel0011 的回复:
Windows API大部分是__stdcall,只有变参函数是__cdecl,因为Windows API是已经编译好的,其调用约定已定
我的意思是,那些函数已经确定,__stdcall导致末尾的ret会修改栈顶对吧? 那么调用方也需要按__stdcall来调用对吧? 但问题在于,我测试调用方按__cdecl来调用,结果也能正常运行? 为了确定gcc确实是按__cdecl编译的,对比__stdcall和__cdecl调用函数后的操作,一个没动作,一个有修改esp的操作,所以它确实按我指示的__cdecl调用了__stdcall函数,但却正常运行了(不是一个函数,是多个)
六道佩恩 2021-05-15
  • 打赏
  • 举报
回复
引用 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应视为没有清理栈,是吧?
Intel0011 2021-05-14
  • 打赏
  • 举报
回复
引用 楼主 六道佩恩 的回复:
我主要有两个问题: 1. 关于变参函数,是不是不管声明何种调用约定,其都会实现为__cdecl?下面是VC的汇编,实际指定的是__stdcall。 2. gcc的对栈清理的方式怎么和VC不一样啊?或者说这算栈清理吗? 观察了它几次调用,发现它每次传参都是拿同一段空间反复使用,就是图中入栈的那段,对不同函数的调用,传参都是用的这同一段,作为调用方把控了传参的栈空间,是不是也相当于做了栈清理?或者说这能算栈清理吗? (编译用的32位,非x64约定)
1、对于变参函数,其调用约定只能为__cdecl,因为只有调用者才知道传了多少个参数,被调用者是无法知道的(有标志意义的参数除外),所以这时也只能由调用者来做栈清理,恢复到调用前的状态 2、每次传参都是拿同一段空间反复使用,这本身就是栈的处理方式,是优于堆的地方,每一次函数调用后,都会恢复到调用前的状态,所以一般情况下栈不需要特别大的空间,函数式编程中的尾递归就是栈的极致利用方式 3、不论是那种调用约定,只要每一次函数调用后将栈恢复到调用前的状态,都可以认为是栈清理,也叫栈平衡
突触 2021-05-14
  • 打赏
  • 举报
回复
1. stdcall 等调用约定的规则就限制了他们进行变参函数的使用 。 当然也有些特殊情况。 2.栈清理 是什么东西?? 参数传递的入栈和出栈吗? gcc 和 vc 传参时对栈的使用基本是相同的 ,只不过 gcc 提前分配了空间 而vc使用push ,但是每次使用完栈都是平衡的 。

69,371

社区成员

发帖
与我相关
我的任务
社区描述
C语言相关问题讨论
社区管理员
  • C语言
  • 花神庙码农
  • 架构师李肯
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧