C-код с неопределенными результатами, компилятор генерирует недопустимый код (с -O3)
Я знаю, что когда вы делаете определенные вещи в программе на C, результаты не определены. Однако компилятор не должен генерировать неверный (машинный) код, верно? Было бы разумно, если бы код делал что-то не так, или если код генерировал ошибку или что-то в этом роде...
Это должно произойти в соответствии со спецификацией компилятора, или это ошибка в компиляторе?
Вот (простая) программа, которую я использую:
int main() {
char *ptr = 0;
*(ptr) = 0;
}
Я собираю с -O3
, Это не должно генерировать недопустимые аппаратные инструкции, правда? С -O0
Я получаю segfault, когда я запускаю код. Это кажется намного более вменяемым.
Изменить: это генерирует ud2
инструкция...
1 ответ
Инструкция ud2 является "допустимой инструкцией" и обозначает Undefined Instruction и генерирует недействительный clang исключения кода операции, и, очевидно, gcc может генерировать этот код, когда программа вызывает неопределенное поведение.
От clang
Ссылка выше обоснования объясняется следующим образом:
Хранение в null и вызовы через нулевые указатели превращаются в вызов __builtin_trap() (который превращается в инструкцию перехвата типа "ud2" на x86). Это происходит все время в оптимизированном коде (в результате других преобразований, таких как встраивание и постоянное распространение), и мы просто удаляли блоки, которые их содержали, потому что они были "явно недоступны".
Хотя (с точки зрения юристов, говорящих на педантичном языке) это абсолютно верно, мы быстро поняли, что люди иногда разыменовывают нулевые указатели, а выполнение кода просто попадает в верхнюю часть следующей функции, что очень затрудняет понимание проблемы. С точки зрения производительности, наиболее важным аспектом их раскрытия является сжатие нижестоящего кода. Из-за этого clang превращает их в ловушку во время выполнения: если один из них фактически достигается динамически, программа немедленно останавливается и может быть отлажена. Недостаток этого состоит в том, что мы слегка раздуваем код, имея эти операции и условия, которые управляют их предикатами.
в конце дня, когда вы вызываете неопределенное поведение, поведение вашей программы непредсказуемо. Философия здесь заключается в том, что, вероятно, лучше терпеть крах и дать разработчику понять, что что-то серьезно, и позволить им отладить правильную точку, чем создавать программу, которая, кажется, работает, но на самом деле не работает.
Как отмечает Руслан, он "действителен" в том смысле, что он гарантированно вызывает недопустимое исключение кода операции, в отличие от других неиспользованных последовательностей, которые в будущем могут стать действительными.