Comments 11
Предложенный способ избавления от UB не избавляет от всех UB. Остаются UB растущие из многопоточности и модели памяти, для их устранения нужно либо полностью запретить многопоточность, либо ввести в язык понятие владения (как в Rust). До тех пор пока в языке есть многопоточность и разделяемая память — UB не избежать, потому что корни UB — в железе.
Но даже в однопоточной программе избавиться от UB не так-то просто: UB потенциально может дать любая операция обращения по указателю, если только в языке нет способа гарантировать что указатель всегда указывает на валидный объект, т.е. ручное управление памятью в таком языке будет под запретом.
Наконец, если все получится и мы сделаем язык без UB — то задача оптимизации программы на этом языке будет сводиться к проблеме останова...
Часть 1 — какой-то странный поток сознания.
Неужели у кого-то были претензии к наличию UB в LLVM? Да ладно. Казалось бы, это промежуточный язык, в котором может быть что угодно — лишь бы он позволял эффективно перевести код на ЯВУ (в котором UB есть) в машинный.
Опять же, заголовок откровенно желтушный. UB — это безусловно небезопасное программирование. Вся суть UB сводится к тому, что его быть не должно, и компилятор может на это полагаться. Так что если вы видите UB в безопасной (а значит, корректной) программе — значит, его обходит какой-нибудь if или ещё что случается, чтобы мы не попали в эту ситуацию. There is no spoon.
И в этом ничего плохого нет, поэтому и получается, что UB != Unsafe.
Тут терминологическая ошибка. "Есть UB" != "Есть операция имеющая UB-случаи"
Простейший пример: разыменование указателя имеет два UB-случая: мусор в указателе и пустой указатель. Но в выражении *(new int)
нет никакого UB, потому что оператор new
всегда возвращает корректный непустой указатель.
Это я понимаю. UB — это такая штука, которой в программе не должно быть, и гарантировать это должен программист. В случае LLVM-кода — программист даёт программу без UB, фронтэнд-компилятор делает из неё LLVM-код без UB (к примеру [насколько я понял], если на входе программа без UB — LLVM-код не сгенерирует poison value), дальше он транслируется в машинный код. На каждом этапе UB нет, или мы имеем GIGO.
Иначе говоря — не может быть UB в LLVM-коде, если во входном коде его нет.
Могут быть ооперации, обладающие UB, но сам компилятор при этом гарантирует, что случаев UB не будет.
Именно. Он получил на входе код, не генерирующий UB — выдал на выходе код, не генерирующий UB.
И если даже где-то в этом коде инструкция, которая может привести к UB — то транслятор следующего уровня может полагаться на то, что она в таком виде не вызовется, и смело оптимизировать.
Вполне могу себе представить даже явное добавление фронтэндом таких инструкций — просто чтобы показать бэкэнду "вот это условие точно не выполняется".
Собственно об этом и статья: большинство UB — это всего лишь незадокументированное поведение. Ну как пример: "
If the implementation does not support negative zeros, the behavior of the &, |, ^, ~, <<, and >> operators with operands that would produce such a value is undefined.
". Такой код будет процессорно-специфичным (непереносимым), но вполне безопасным для конкретного процессора и компилятора.
Неопределённое поведение != Небезопасное программирование