如何在c++中检测对作用域外堆栈变量的引用
How to detect references to out-of-scope stack variables in C++?
Valgrind用于检测对堆上已释放对象的残留引用。然而,对于堆栈上对范围外变量的持久引用,它似乎没有这个特性。例如:
#include <iostream>
struct CharHolder {
const char ch;
CharHolder(char _ch) : ch(_ch) {}
};
struct Printer {
const CharHolder& ref;
Printer(const CharHolder& _ref) : ref(_ref) {}
void print() {
std::cout << &ref << ": " << ref.ch << std::endl;
}
};
int main() {
// g++ -O0: prints 'x'
// g++ -O3: prints undefined character
Printer p1(CharHolder('x'));
p1.print();
// g++: prints undefined character
CharHolder* h = new CharHolder('x');
Printer p2(*h);
delete h;
p2.print();
}
第一个例子是p1
,在这个例子中,打印机持有一个对作用域外堆栈变量的引用,因为一旦p1
的构造完成,CharHolder('x')
就会被销毁。
第二个例子,在p2
中,打印机持有对堆变量的引用,在p2
试图在print()
中引用它之前,该变量被释放。
Valgrind抱怨第二个例子:
==82331== Invalid read of size 1
==82331== at 0x400A8E: Printer::print()
==82331== by 0x400967: main
==82331== Address 0x5a1c040 is 0 bytes inside a block of size 1 free'd
==82331== at 0x4C2C2BC: operator delete(void*)
==82331== by 0x40095F: main
如何检测第一种错误,也许使用像Valgrind这样的工具?
没有一个静态分析工具是完美的。像valgrind
这样的静态分析工具在捕捉常见的编程错误方面有着很好的记录。
但是他们不能100%的抓住它们。
我试图尽可能避免产生这类编程错误的方法是防御性编程原则,其目的是通过契约证明,这类编程错误在逻辑上是不可能的。这包括如下内容:
-
使用智能指针代替引用和指针。你可以通过契约证明,使用智能指针会导致对超出作用域的对象的引用在逻辑上变得不可能。
-
使用迭代器和标准库算法,而不是经典的
for (size_t i=0; i<container.size(); ++i)
方法。有了明确定义的开始和结束迭代器,在逻辑上就不可能在数组的末尾运行了。另外,作为额外的奖励,如果由于某种原因,容器的选择被切换,代码将需要更少的更改。
在您的情况下,仅运行时静态分析工具几乎不可能检测到这一点。最终编译的代码绝对不包含任何在运行时正式将临时标记为超出作用域的内容。生成的代码分配一个堆栈帧,足以容纳自动作用域变量和作为参数传递的临时变量。构造函数调用完成后,不会生成显式调用来将临时对象标记为销毁。我不明白valgrind
或任何其他静态分析工具是如何知道这一点的。
如果临时的类有显式析构函数,理论上泛型静态分析工具可以通过调用类实例的析构函数来知道类实例现在被销毁了。
但这告诉你没有完美的答案。即使是我提到的编程实践也不能100%避免问题;而且它们有时会引入自己的复杂性,必须加以考虑(如使用智能指针时的循环引用)。
- 堆栈变量超出范围时是否解除分配?
- 堆栈变量和函数C++奇怪的行为
- 野牛堆栈变量中的值分配
- 局部堆栈变量成员的返回值优化
- 访问其他线程堆栈变量如何在C++中工作?
- 指向堆栈变量的指针是否易失
- 是否可以 std::move 局部堆栈变量?
- 返回堆栈变量时停止调用析构函数
- 优化易失性堆栈变量的存储/构造是否合法
- SEGFAULT正在写入堆栈变量
- 汇编:C++堆栈变量地址不同/错误?
- C++11 Lambda闭包通过引用涉及一个堆栈变量,该变量离开作用域是允许的,但得到了未定义的行为
- 返回堆栈变量
- Visual Studio 在引用堆栈变量时不使用 EBP
- 堆栈变量或函数声明
- 访问堆栈变量的速度比取消引用指针慢
- 静态变量与堆栈变量:线程安全与堆栈大小
- 堆栈变量生存期好奇的例子
- 如何查找未初始化的堆栈变量
- 如何为堆栈变量分配内存