C++中取悬空引用的地址是否属于未定义行为?
问题解答:取悬空引用的地址是否属于C++未定义行为
结论
取悬空引用的地址属于未定义行为,相关规则可以在C++标准的[basic.life]章节查询。
规则解释
C++标准[basic.life]第6条明确规定:
在对象生命周期结束后,且该对象占用的存储尚未被复用或释放之前,仅可在有限场景下使用指向原对象的泛左值(glvalue):仅使用不依赖对象值的泛左值属性是明确定义的,一旦出现访问对象、调用对象非静态成员函数、进行不符合要求的类型转换等操作,均属于未定义行为。
若对象占用的存储已经被释放或复用,则任何指向该对象的泛左值的使用,都没有明确定义,属于未定义行为。
引用本身是对象的别名,对引用取地址等价于对引用绑定的对象取地址。当引用绑定的局部变量生命周期结束、对应的栈存储已经随函数调用结束被释放时,该引用成为悬空引用,此时对该引用执行取地址操作,已经不符合[basic.life]中存储尚未释放的前提,因此属于未定义行为。
两个示例的差异说明
第一个示例代码
#include <iostream> void foo() { int i; static auto f = [&i]() { std::cout << &i << "\n";}; f(); } int main() { foo(); foo(); }
该代码中静态lambda仅在第一次调用foo时初始化,捕获了第一次调用时的局部变量i的引用。第二次调用foo时,第一次的i已经随第一次foo调用结束销毁、对应栈存储已经释放,lambda持有的引用已经悬空,此时调用lambda取地址属于未定义行为。
gcc没有给出警告是因为静态变量延迟初始化的特性增加了静态分析的难度,编译器没有检测到悬空问题,不代表代码行为合法。运行时两次输出相同地址只是栈内存复用的巧合,属于未定义行为的常见表现。
第二个示例代码
#include <iostream> auto bar() { int i; return [&i]() {std::cout << &i << "\n"; }; } int main() { bar()(); }
该代码中函数返回的lambda直接捕获了局部变量i的引用,bar返回时i已经销毁,引用直接悬空,这类场景属于编译器静态分析可以轻易覆盖的场景,因此gcc会直接给出返回栈地址的警告(开启-Werror时转为错误)。
内容的提问来源于stack exchange,提问作者463035818_is_not_an_ai
相关产品推荐
相关产品推荐

