Функция 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 на первую статью , на вторую статью и
третья статья .