书上都说不能返回局部变量的引用或局部指针,说这种行为危险,但又没讲具体原因,那么今天就来看看这种行为的具体细节
PS:下面含有AT&T汇编内容,未学过汇编的朋友可以跳过直接看结论
先放一个实验用函数,即返回 int&
类型的局部变量的函数
int& RetInt(){
int i=42;
int& RefI=i;
return RefI;
}
稍后会用到的几个测试函数,用来说明不同情况下使用局部的结果
void nop(){
int i=0,i2=12;
}
void Snop(){
int i=3;
int&ri=i;
}
void Skip(){
const double&d=4.2;
}
主函数如下(待会使用控制变量法)
调用 nop
函数
/**
* @brief FileName:main.cpp
* environment : g++&Ubuntu18.04
*/
int main(){
std::cout<<RetInt()<<std::endl;
int& i=RetInt();
nop();
// Skip();
// Snop();
std::cout<<i<<std::endl;
return 0;
}
$ g++ main.cpp -omain;./main #编译指令
输出结果为:
42
42
即两次使用返回的局部变量引用都表现正常
这时候回顾前瞻知识:
int i=42;
int& iref=i;
int* ip=&i;
movl $42, -36(%rbp) ;int i=42;
leaq -36(%rbp), %rax ;leaq 为汇编取地址操作
movq %rax, -32(%rbp) ;int& iref=i;//可见引用占的大小和 类型(int)大小是相同的
leaq -36(%rbp), %rax
movq %rax, -24(%rbp) ;int* ip=&i;//指针为 8 个 字节
解释
下面看一下汇编的具体细节
可以看到之前函数所用内存:-20(%rbp) -16(%rbp) 是没有改变的
通过黄框可以明显看到调用 Snop
函数的时候定义变量和引用都是用的之前[上一个调用时]使用的内存地址,即发生了覆盖读写,main函数中的 i 的值发生变化
这时似乎可以怀疑是 int i=RetInt();
时,这时的 i 已经是3了
那么修改了一下main函数
结果不变,和上面是一样啊
到这里就基本验证了之前的解释
只声明变量的函数未覆盖之前销毁的对象地址
而如果使用了引用则覆盖地址
42
1074842828
将
Skip
函数中常量引用 d 的值改为 0.0 后,输出第二行为 0
如果第二次实验只是改变了数值,还勉强能看到值的由来的话,那这个 4.2 带来的值的变化,是着实看不懂了
其实的话,是用double类型浮点数的形式存储 4.2 然后用int类型的形式去读取相应内存单元的结果
可以看出显示的是Skip函数定义的常量引用的高位地址的值
也许会有人质疑double类型的会影响是因为double 八位,相当于 int 与 int& 的总和
于是对 Skip
函数稍加更改
输出结果:
42
4
RetInt 函数的汇编没有变化
std:cout< 不然没有语法检测的报错和警告,是很容易找不到问题所在的。
int retRef(){
int i;
return i;
}
int main(){
std::cout<<retRef()<<std::endl;
}