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
, это тот, где принтер содержит ссылку на переменную стека вне области видимости, так как CharHolder('x')
разрушается, как только строительство p1
завершено.
Второй пример, с p2
, это тот, где принтер хранит ссылку на переменную кучи, которая до p2
пытается сослаться на это в print()
,
Вальгринд жалуется на второй пример:
==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% проблем; и иногда они вводят свою собственную сложность, которую необходимо учитывать (например, циклические ссылки при использовании умных указателей).
Других решений пока нет …