找回密码
 立即注册→加入我们

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 2390|回复: 0

测试编译器下限之不同大小返回值的处理

[复制链接]

307

主题

228

回帖

7349

积分

用户组: 真·技术宅

UID
2
精华
76
威望
291 点
宅币
5599 个
贡献
253 次
宅之契约
0 份
在线时间
949 小时
注册时间
2014-1-25
发表于 2014-8-10 00:02:41 | 显示全部楼层 |阅读模式

欢迎访问技术宅的结界,请注册或者登录吧。

您需要 登录 才可以下载或查看,没有账号?立即注册→加入我们

×
写论文ing...
总结出一段话,贴在这里:
        编译器会根据函数返回类型大小(sizeof操作符进行4字节对齐后的值)不同进行不同的操作。如果该值为4字节,那么默认会返回到EAX寄存器中,如果该值为8字节,则通常使用EAX寄存器装载结果的低4字节,EDX寄存器装载结果的高4字节。如果该值超过8,则根据编译器的不同会有不同的行为,在剩余寄存器足够用的情况下,通常会使用这些寄存器来传值,如果编译器发现现有寄存器无法满足返回值长度要求,那么就会在内存中开辟一块相对安全的栈区域用于存储该值,赋值完毕后,将起始位置指针传递给EAX。一般情况下,返回大小超过8字节的行为并不恰当,如果在设计不良的编译器中很有可能造成返回地址覆盖,也极为少见。(基于32位 MSVC)
        对于4字节返回值,例如int,众所周知,返回值存在EAX
        对于8字节返回值,例如longlong,众所周知,返回值存于EDX:EAX
        对于8字节以上返回值,虽然编程方法错误(因为有些不良编译器可能直接返回栈地址),但是出于理论研究,还是想看看编译器怎么考虑:

对于20字节返回值,做个测试:
  1. struct p
  2. {
  3.         int i;
  4.         int j;
  5.         int k;
  6.         int l;
  7.         int m;
  8. };
  9. p func()
  10. {
  11.         p p1={1,2,3,4,5};
  12.         return p1;
  13. }

  14. void main()
  15. {
  16.         func();
  17. }
复制代码

进入函数后可发现:
  1. 19:   p func()
  2. 20:   {
  3. 0040D4A0   push        ebp
  4. 0040D4A1   mov         ebp,esp
  5. 0040D4A3   sub         esp,54h
  6. 0040D4A6   push        ebx
  7. 0040D4A7   push        esi
  8. 0040D4A8   push        edi
  9. 0040D4A9   lea         edi,[ebp-54h]
  10. 0040D4AC   mov         ecx,15h
  11. 0040D4B1   mov         eax,0CCCCCCCCh
  12. 0040D4B6   rep stos    dword ptr [edi]
  13. 21:       p p1={1,2,3,4,5};
  14. 0040D4B8   mov         dword ptr [ebp-14h],1
  15. 0040D4BF   mov         dword ptr [ebp-10h],2
  16. 0040D4C6   mov         dword ptr [ebp-0Ch],3
  17. 0040D4CD   mov         dword ptr [ebp-8],4
  18. 0040D4D4   mov         dword ptr [ebp-4],5
  19. 22:       return p1;
  20. 0040D4DB   mov         ecx,5
  21. 0040D4E0   lea         esi,[ebp-14h]
  22. 0040D4E3   mov         edi,dword ptr [ebp+8]
  23. 0040D4E6   rep movs    dword ptr [edi],dword ptr [esi]
  24. 0040D4E8   mov         eax,dword ptr [ebp+8]
  25. 23:   }
  26. 0040D4EB   pop         edi
  27. 0040D4EC   pop         esi
  28. 0040D4ED   pop         ebx
  29. 0040D4EE   mov         esp,ebp
  30. 0040D4F0   pop         ebp
  31. 0040D4F1   ret
复制代码


0040D4A9   lea         edi,[ebp-54h]
如果从头单步调试的话,你会发现这句居然在父函数未使用的栈空间(通过ebp得到)分配了一段空间。
返回值也不存在那些个寄存器里了,而是填充完毕栈以后,将首地址返回给EAX

对16字节、12字节、8字节测试,效果同上,栈空间分配减少字节而已

通常,对于8字节以上的返回值,都不会通过按值传递来返回,而是按引用传递,这样做一方面可以避免编译器返回局部变量地址,
另一方面用地址返回速度较快,不用一个个值拷贝。考虑到函数结束后,栈应该由系统收回,所以不应该再使用,因此不能让其传递为当前函数栈变量;
基于上述2点,需要返回的地址,需要在调用参数时由父函数提供,这也是最正规的,不会产生bug的思路,举个例子:

struct pt
{
        int x;
        int y;
        int z;
}

const pt& Getpt(pt& src);
该函数修改src内容,并返回src,由于src是父函数提供,因此不会在Getpt解析后造成栈释放,是一种安全调用方式。
回复

使用道具 举报

QQ|Archiver|小黑屋|技术宅的结界 ( 滇ICP备16008837号 )|网站地图

GMT+8, 2024-5-5 07:22 , Processed in 0.041508 second(s), 31 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表