Я работаю над программой на 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
Результат
Валгринд сообщил о большом количестве 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 сообщает о «недопустимом чтении» (как и ожидалось). Я чувствую, что проблема заключается в размере векторных резервов, но я не совсем понимаю, почему/что это такое.
Насколько я понимаю, вы вызываете резерв для элементов DATA_SIZE * 2, но не инициализируете их все. Правильно ли я понимаю @Yksisarvinen?
@kiner_shah Цикл ниже инициализирует элементы по индексам от 0 до DATA_SIZE-1, затем в основном они пытаются прочитать индекс DATA_SIZE (первый неинициализированный). Код как есть, он читает из допустимой памяти, но без допустимого объекта. Когда OP изменяется reserve, вызывается только резерв DATA_SIZE, чтение происходит из недействительной памяти.
Это не выходит за рамки, поскольку у вас есть место (как минимум) для DATA_SIZE * 2 элементов. Первое «недопустимое чтение» находится в data[data.capacity()], а не в data[data.size()].
@Yksisarvinen, такое же понимание. Спасибо за разъяснения.





Memcheck работает на гораздо более низком уровне, чем исходный код высокого уровня. Он ничего не знает о std::vector. Все, что он видит, — это выделения (с оператором new), а также чтение и запись (а также системные вызовы). Поэтому, когда вы пишете data_.reserve(DATA_SIZE * 2);, memcheck перенаправит базовый вызов на оператора 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 и так далее). Эти проверки условий для неинициализированных значений вызовут ошибки. (Возможно, это не те места, где возникают ошибки, это просто пример).
Я не эксперт, но насколько я понимаю, "недопустимое чтение" означает, что вы обращаетесь к памяти, которая вам не принадлежит. В данном случае память принадлежит вам, но инициализированного объекта там нет.