Я работаю над программой на 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 и так далее). Эти проверки условий для неинициализированных значений вызовут ошибки. (Возможно, это не те места, где возникают ошибки, это просто пример).
Я не эксперт, но насколько я понимаю, "недопустимое чтение" означает, что вы обращаетесь к памяти, которая вам не принадлежит. В данном случае память принадлежит вам, но инициализированного объекта там нет.