- UID
- 2
- 精华
- 积分
- 7736
- 威望
- 点
- 宅币
- 个
- 贡献
- 次
- 宅之契约
- 份
- 最后登录
- 1970-1-1
- 在线时间
- 小时
|
写论文ing...
总结出一段话,贴在这里:
编译器会根据函数返回类型大小(sizeof操作符进行4字节对齐后的值)不同进行不同的操作。如果该值为4字节,那么默认会返回到EAX寄存器中,如果该值为8字节,则通常使用EAX寄存器装载结果的低4字节,EDX寄存器装载结果的高4字节。如果该值超过8,则根据编译器的不同会有不同的行为,在剩余寄存器足够用的情况下,通常会使用这些寄存器来传值,如果编译器发现现有寄存器无法满足返回值长度要求,那么就会在内存中开辟一块相对安全的栈区域用于存储该值,赋值完毕后,将起始位置指针传递给EAX。一般情况下,返回大小超过8字节的行为并不恰当,如果在设计不良的编译器中很有可能造成返回地址覆盖,也极为少见。(基于32位 MSVC)
对于4字节返回值,例如int,众所周知,返回值存在EAX
对于8字节返回值,例如longlong,众所周知,返回值存于EDX:EAX
对于8字节以上返回值,虽然编程方法错误(因为有些不良编译器可能直接返回栈地址),但是出于理论研究,还是想看看编译器怎么考虑:
对于20字节返回值,做个测试:
- struct p
- {
- int i;
- int j;
- int k;
- int l;
- int m;
- };
- p func()
- {
- p p1={1,2,3,4,5};
- return p1;
- }
- void main()
- {
- func();
- }
复制代码
进入函数后可发现:
- 19: p func()
- 20: {
- 0040D4A0 push ebp
- 0040D4A1 mov ebp,esp
- 0040D4A3 sub esp,54h
- 0040D4A6 push ebx
- 0040D4A7 push esi
- 0040D4A8 push edi
- 0040D4A9 lea edi,[ebp-54h]
- 0040D4AC mov ecx,15h
- 0040D4B1 mov eax,0CCCCCCCCh
- 0040D4B6 rep stos dword ptr [edi]
- 21: p p1={1,2,3,4,5};
- 0040D4B8 mov dword ptr [ebp-14h],1
- 0040D4BF mov dword ptr [ebp-10h],2
- 0040D4C6 mov dword ptr [ebp-0Ch],3
- 0040D4CD mov dword ptr [ebp-8],4
- 0040D4D4 mov dword ptr [ebp-4],5
- 22: return p1;
- 0040D4DB mov ecx,5
- 0040D4E0 lea esi,[ebp-14h]
- 0040D4E3 mov edi,dword ptr [ebp+8]
- 0040D4E6 rep movs dword ptr [edi],dword ptr [esi]
- 0040D4E8 mov eax,dword ptr [ebp+8]
- 23: }
- 0040D4EB pop edi
- 0040D4EC pop esi
- 0040D4ED pop ebx
- 0040D4EE mov esp,ebp
- 0040D4F0 pop ebp
- 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解析后造成栈释放,是一种安全调用方式。 |
|