Почему Valgrind не обнаруживает использование неинициализированной переменной? - PullRequest
0 голосов
/ 28 апреля 2018

Как я понимаю, Valgrind должен сообщать об ошибках, когда код содержит использование неинициализированных переменных. В приведенном ниже примере с игрушкой printer неинициализирован, но программа все равно «с радостью» печатает сообщение.

#include <iostream>

class Printer {
    public:
        void print() {
            std::cout<<"I PRINT"<<std::endl;
        }
};


int main() {
    Printer* printer;
    printer->print();
};

Когда я тестирую эту программу с Valgrind, она не сообщает об ошибках.

Это ожидаемое поведение? И если да, то почему?

Ответы [ 2 ]

0 голосов
/ 28 апреля 2018

Функция main() имеет неопределенное поведение, поскольку printer не инициализирован, и оператор printer->print() обращается к значению printer и отменяет обращение к нему через -> и вызов функции-члена.

Однако на практике компилятору разрешено обрабатывать неопределенное поведение, просто предполагая, что его нет. Тогда компилятор может, если захочет, следовать цепочке логики;

  • Когда он видит оператор типа printer->print(), это означает, что ему разрешено рассуждать о том, что printer имеет значение, к которому можно обращаться и разыменовывать, не вводя неопределенное поведение.
  • Исходя из этого, можно допустить, что printer должен быть инициализирован (каким-то образом невидимым для компилятора), чтобы указывать на действительный объект.
  • Исходя из этого предположения, это может привести к тому, что оператор printer->print() приведет к вызову Printer::print().
  • Поскольку компилятор может видеть определение Printer::print(), он может просто вставить его и выполнить инструкцию std::cout<<"I PRINT"<<std::endl.
  • Так как ему вообще не нужен доступ к printer для получения этого вывода, он может оптимизировать любую ссылку на переменную с именем printer в main().

Если компилятор следует вышеуказанной последовательности логики, программа просто напечатает I PRINT и завершит работу, не обращаясь к какой-либо памяти таким образом, который может вызвать отчет от Valgrind.

Если вы считаете, что вышеприведенное звучит надуманно, то вы ошибаетесь. LLVM / Clang - это один компилятор, который условно следует цепочке логики, очень похожей на то, что я описал. Для получения дополнительной информации просмотрите ссылку на блог проекта LLVM на первую статью , на вторую статью и третья статья .

0 голосов
/ 28 апреля 2018

Переменная фактически никогда не используется.

  1. Вызов метода встроен 1 , поэтому переменная не передается в качестве аргумента.
  2. Сам метод никак не использует this, поэтому переменная вообще не используется.

Выше не зависит от включения или выключения оптимизации.

На самом деле, в оптимизированном коде переменная вообще никогда не будет существовать - даже в качестве выделения памяти.

Вопрос о похожем случае: Переменная Extern только в заголовке неожиданно работает, почему? .


1 Все методы, определенные в теле класса, встроены по умолчанию.

Это неопределенное поведение?

Да, это так. Для вызова метода требуется this, чтобы указать на фактическое, инициализированное происхождение объекта, чтобы он был правильно сформирован. Как указывает Нир Фридман, компилятор может предположить, что и оптимизировать на этой основе (и IIRC такого рода оптимизации могут произойти даже с -O0!).

Я бы лично ожидал, что конкретный рассматриваемый код будет работать в любых практических условиях (поскольку значение указателя действительно не имеет значения), но я бы никогда не стал полагаться на это. Вы должны исправить свой код прямо сейчас.

*

Обнаружение 1037 * Чтобы обнаружить использование неинициализированных переменных в Clang / GCC, используйте параметр -Wuninitialized (или просто используйте -Wall, который включает этот флаг). -Wuninitialized в основном должно охватывать использование памяти, выделенной из стека, хотя я предполагаю, что некоторое использование массивов, выделенных из стека, все еще может не работать. Некоторые компиляторы могут поддерживать включение дополнительных проверок времени выполнения для неинициализированных операций чтения с параметрами -fsanitize=..., такими как -fsanitize=memory в Clang (thx, chtz ). Эти проверки должны охватывать крайние случаи, а также использование памяти, выделенной в куче.

...