测试编译器下限之不同大小返回值的处理
写论文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,
0040D4AC mov ecx,15h
0040D4B1 mov eax,0CCCCCCCCh
0040D4B6 rep stos dword ptr
21: p p1={1,2,3,4,5};
0040D4B8 mov dword ptr ,1
0040D4BF mov dword ptr ,2
0040D4C6 mov dword ptr ,3
0040D4CD mov dword ptr ,4
0040D4D4 mov dword ptr ,5
22: return p1;
0040D4DB mov ecx,5
0040D4E0 lea esi,
0040D4E3 mov edi,dword ptr
0040D4E6 rep movs dword ptr ,dword ptr
0040D4E8 mov eax,dword ptr
23: }
0040D4EB pop edi
0040D4EC pop esi
0040D4ED pop ebx
0040D4EE mov esp,ebp
0040D4F0 pop ebp
0040D4F1 ret
0040D4A9 lea edi,
如果从头单步调试的话,你会发现这句居然在父函数未使用的栈空间(通过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解析后造成栈释放,是一种安全调用方式。
页:
[1]