CppCoreGuidelines F.43 不要(直接或间接)返回指向局部对象的指针或引用
24 June 2022
理由
悬空指针会导致程序崩溃,数据破坏。
坏例子
从函数返回后,本地对象已经没了。
// -*- compile-command: "g++ -std=c++20 code.cpp && ./a"; -*- #include <iostream> #include <gsl/gsl> using namespace std; 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
是同一类型的变体- 一组函数调用过程中有更多悬挂指针
- 黑客可以利用悬挂指针做什么事情
幸运的是,大部分现代编译器都会对简单的情况进行警告
注意
引用有一样的情况
// -*- compile-command: "g++ -std=c++20 code.cpp && ./a"; -*- #include <iostream> #include <gsl/gsl> using namespace std; 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; }
注意
只针对非静态局部变量会。静态变量静态分配存储空间,指向他们的指针不会悬空。
坏例子
有些泄漏局部变量指针的情况不太明显
// -*- compile-command: "g++ -std=c++20 code.cpp && ./a"; -*- #include <iostream> #include <gsl/gsl> using namespace std; 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 = 0 address of i = 0xbdc97ffb0c 99 address of glob = 0xbdc97ffb0c
这里我们在函数 f()
中把本地变量的引用赋值给了 glob
,后续代码使用 glob
会产生不可预测的结果。
注意
本地变量地址会通过返回、 T&
输出参数、返回对象的成员、返回数组的元素等方式泄漏出来。
注意
内部作用域的变量,也可能通过类似方式泄漏到外层作用域。
此问题的另一个变体是将指针放在一个容器内,此容器的生存周期比指针指向的对象生存周期长。
强化
- 编译器捕捉返回值为指向局部对象的引用和指针。
- 静态代码分析确认用指针表示对象位置的情况。