#include<iostream>
using namespace std;
void print()
{
cout<<"hello world"<<endl;
}
void test()
{
int arr[1];
arr[2]=(int)print;
}
int main()
{
test();
return 0;
}
以上代码,运行结果为:
[Hyman@Hyman-PC cplus]$ g++ array.cpp
[Hyman@Hyman-PC cplus]$ ./a.out
hello world
问题:为什么上述代码中根本没有调用print函数的语句,但是却打印出来了hello world的?
在上一篇文章《浅析C++中函数调用时的内存分配-函数局部变量的内存分配》 中,最后留下了一个很有意思的问题,在代码中没有任何一个语句调用了 print() 函数,结果 print() 函数却被执行了。现在就对该问题进行分析:
首先,我们先说明下C++中内存结构的分布,如下图所示:
整个内存从低地址到高地址一次分为代码段、数据区、堆区、栈区、和命令行参数、系统变量存储区。
代码段负责存放程序的各种指令,数据区主要存放全局数据、静态数据,而堆区是进行内存分配的地方,栈区是在进行局部变量存放的地方(栈顶是动态变化的),最上面的是存放系统变量和命令行参数等等。
我们再看一下在执行一个函数时,函数怎么在栈区分配地址:
在函数执行过程中,首先在栈中放入函数调用前的各个寄存器的执行状态,因为函数结束时需要将函数寄存器的值回到原位,然后存入形参(这点还有争议,和我之前看过的资料不一样,资料上说形参应该存到局部变量前面,但是我通过做实验并查看相关汇编代码,实际形参的存储位置应该如图),然后存入函数的返回地址(所谓返回地址就是函数结束后的下一条语句地址),然后是函数返回值(为返回值预留空间),最后是函数的局部变量。
在这个例子中,我们定义了一个只含有一个元素的整形数组arr[1],然后数组越界把arr[2]存放了函数print的地址。
先看此时的内存分布(只看局部变量部分):
从图中可以看到,我们将print的函数地址放入了本该是函数返回地址的地方,这就导致test函数一结束就调用了print函数,也就打印出了”hello world”,这也就是在我们看来为什么没有调用函数print函数的语句,print函数却被调用的原因了。