我正在开发一个 C++ 程序,该程序涉及访问 std::vector 中的元素。我在使用 Valgrind 运行代码时遇到了一个问题,它报告“未初始化值”错误,但不是我预期的“无效读取”错误。这是我的代码的简化版本:
#include <vector>
#include <iostream>
#define DATA_SIZE 10
struct Data {
Data(double aa, double bb) : a(aa), b(bb) {}
double a {0.0};
double b {0.0};
};
class Test {
public:
Test() {
data_.clear();
data_.reserve(DATA_SIZE * 2);
for (int i = 0; i < DATA_SIZE; ++i) {
data_.push_back(Data(i, i));
}
}
double Read() {
const int index = DATA_SIZE;
const auto& data = data_[index];
double res = data.a + data.b;
std::cout << res;
return res;
}
private:
std::vector<Data> data_;
};
int main() {
Test t;
t.Read();
}
Machine
:Linux ubuntu 18.04,带有 g++ 7.5
和 valgrind 1:3.13.0-2ubuntu2.3
Compile
g++ -std=c++17 -O2 -g test.cpp -o test
Run
valgrind --tool=memcheck --leak-check=full --expensive-definedness-checks=yes --track-origins=yes ./test
结果
Valgrind 在
Conditional jump or move depends on uninitialised value(s)
上报告了很多 operator<<(std::cout << res;)
。
问题:
我期望 Valgrind 报告“无效读取”,因为访问超出范围,但它只报告“未初始化值”错误。为什么 Valgrind 报告“未初始化值”而不是“无效读取”?
更多信息:
有趣的是,如果我改变
data_.reserve(DATA_SIZE * 2);
到 data_.reserve(DATA_SIZE);
,Valgrind 报告“无效读取”(如预期)。我觉得问题在于矢量储备的大小,但我不太清楚为什么/那是什么。
Memcheck 的工作级别比高级源代码低得多。它对 std::vector 一无所知。它看到的只是分配(使用operator new)以及读取和写入(以及系统调用)。因此,当您编写
data_.reserve(DATA_SIZE * 2);
memcheck 时,会将底层调用重定向到operator new 并记录以默认对齐方式分配的 320 字节。在内部,它将分配标记为未初始化的影子内存。
当你循环浏览时
for (int i = 0; i < DATA_SIZE; ++i) {
data_.push_back(Data(i, i));
}
底部 160 字节被标记为已初始化。然后当你阅读时
const int index = DATA_SIZE;
const auto& data = data_[index];
它仍在 320 字节块的范围内,因此可以访问。但影子内存表明它尚未初始化。不过还没有错误,memcheck 允许复制未初始化的内存。
当您到达
cout
时,您就会收到错误消息。在从 double 到某种字符串的转换过程中,会出现几个条件(是否为负数?指数是否为负数?指数是否大于 1e6 等等)。对未初始化值的这些条件检查将触发错误。 (这些可能不是触发错误的确切位置,这只是一个示例)。