CppCoreGuidelines F.43 不要(直接或间接)返回指向局部对象的指针或引用
24 June 2022
F.43: Never (directly or indirectly) return a pointer or a reference to a local object
理由
悬空指针(dangling pointer)会导致程序崩溃,数据破坏。
例子
虽然我们可以把函数内部的局部变量的地址返回出去,但是在函数执行完后,局部变量的内存已经被覆盖或者清空了。
#include <gsl/gsl> using namespace gsl; int* f() { int fx = 9; return &fx; // BAD } void g(int* p) // looks innocent enough { int gx; cout << "*p == " << *p << '\n'; *p = 999; cout << "gx == " << gx << '\n'; } void h() { int* p = f(); cout << *p; int z = *p; // read from abandoned stack frame (bad) g(p); // pass pointer to abandoned stack frame to function (bad) } int main() { h(); return 0; }
这里,我们可能会得到如下结果:
*p == 999 gx == 999
原因可能是g()
重用了f()
函数不再使用的堆栈空间。所有*p
指向了 gx
的堆栈空间。
那么,我们再考虑下如下几种情况:
fx
和gx
不是同一类型fx
和gx
是同一类型的变体- 这个悬空指针在一组函数之间传来传去
- 这个悬空指针被黑客发现了,会用来做什么事情
幸运的是,大部分现代编译器都会对简单的情况进行警告
注意
返回值是引用的话,也会出现一样的问题
#include <gsl/gsl> using namespace gsl; int& f() { int x = 7; // ... return x; // Bad: returns reference to object that is about to be destroyed } int main() { cout << f(); return 0; }
7
注意
静态局部变量的地址或引用返回不会出现此类问题。因为静态变量静态地分配了存储空间,指向他们的指针不会悬空。
#include <gsl/gsl> using namespace gsl; int& f() { static int x = 7; return x; } int* g() { static int x = 7; cout << &x << '\n'; return &x; } int main() { cout << f() << '\n'; cout << *g() << '\n'; cout << g() << '\n'; return 0; }
7 0x100328004 7 0x100328004 0x100328004
例子
有些局部变量指针悬空的情况不太明显
#include <gsl/gsl> using namespace gsl; int* glob; // global variables are bad in so many ways template<class T> void steal(T x) { glob = x(); // BAD } void f() { int i = 99; cout << "address of i = " << &i << endl; steal([&] { return &i; }); } int main() { cout << "address of glob = " << glob << endl; f(); cout << *glob << '\n'; cout << "address of glob = " << glob << endl; }
address of glob = 0x0 address of i = 0x16ae6b2bc 99 address of glob = 0x16ae6b2bc
这里我们在函数f()
中把局部变量的引用赋值给了 glob
,后续代码使用 glob
会产生一些不确定的结果。
注意
局部变量地址会通过返回、T&
入出参数、返回对象的成员、返回数组的元素等方式泄漏出来。
注意
内层作用域的变量,也可能通过类似方式泄漏到外层作用域。
此问题的另一个变体是将指针放在一个容器内,此容器的生存周期却比指针指向的对象的生存周期长。
强化
- 编译器捕捉返回值为指向局部对象的引用和指针。
- 静态代码分析确认用指针表示对象位置的情况。