钱能c++以 局部函数 引出 函数调用机制中疑惑解答

来源:互联网 发布:淘宝订单怎么评价 编辑:程序博客网 时间:2024/06/06 10:48

钱能c++ 局部函数

 

#include<iostream.h>
int func1();
int func2();
void main()
{
func1();
cout<<func2()<<endl;
}
int func1()
{
int n=12345;
return n;
}
int func2()
{
int m;
return m;
}

会输出什么呢?
钱能的书中讲述输出 12345。

测试结果

VC6:

Debug:
-858993460
Press any key to continue

Release:
4199033
Press any key to continue

 

WHY?


C++已有很多经典著作。

1.如果没有优化
func1()返回以后,是有12345在栈里。这时候还没有覆盖。
但是接下来调用fun2(),会有一系列的PUSH,前面的数据已经覆盖。
返回值完全取决于eax赋值前的那个PUSH。

因为没有初始化,所以取决于编译器怎么编译。
VC6是采用ecx的值给它初始化。
如下这样的一种编译, 打印值是ecx的值。

// fun1()
PUSH EBP
MOV EBP, ESP
PUSH ECX
MOV DWORD PTR [EBP-4], 3039
MOV EAX, DWORD PTR [EBP-4]
MOV ESP, EBP
POP EBP
RETN

int func1()
{
int n=12345;
return n;
}

// fun2()
PUSH EBP
MOV EBP, ESP
PUSH ECX
MOV EAX, DWORD PTR [EBP-4] //EAX的值与刚才ECX的值一致
MOV ESP, EBP
POP EBP
RETN

int func2()
{
int m;
return m;
}

2.如果优化
编译器会优化这个函数,直接mov 到eax返回,根本不会有12345在栈里。
MOV EAX,3039
RETN

int func1()
{
int n=12345;
return n;
}


// 后面还是取决于push
// 这时候 EAX取了栈里的值,自然也不是12345
可以这样:
PUSH ECX
MOV EAX, DWORD PTR [ESP]
POP ECX
RETN

也可以这样优化
PUSH ECX
MOV EAX, DWORD PTR [ESP]
ADD ESP, 4
RETN

或者这样
SUB ESP,4
MOV EAX, DWORD PTR [ESP]
ADD ESP, 4
RETN

甚至这样:
MOV EAX, DWORD PTR [ESP - 4]
RETN

int func2()
{
int m;
return m;
}

 

的确是编译器优化导致的,
早期的编译器,都是把局部变量都放在堆栈中了,对esp指针的移动也是通过sub指令来操作的,因此就一定会输出12345
由于对内存操作的缓慢,现在编译器会把一些局部变量放到寄存器中,就会导致这种现象
还有就是移动esp指针的问题,现在对函数内部只有一两个变量的情况,也不用sub指令了,而是直接用push reg指令了,这样可以减少代码长度,sub指令至少要3个字节,而push reg只需一个字节,不过这种替换虽然可以减小程序体积,但是降低了程序效率,push reg指令要直接操作内存,慢,花费时钟周期多...