Считается ли подписанное целочисленное переполнение в безопасном Rust в режиме выпуска неопределенным поведением?

Rust по-разному обрабатывает подписанное целочисленное переполнение в режиме отладки и выпуска. Когда это происходит, Rust паникует в режиме отладки, в то время как молча выполняет двойное дополнение в режиме выпуска.

Насколько мне известно, C/C++ рассматривает подписанное целочисленное переполнение как неопределенное поведение отчасти потому, что:

  1. В то время стандартизации C другая базовая архитектура представления целых чисел со знаком, такая как дополнение, все еще могла где-то использоваться. Компиляторы не могут делать предположений о том, как аппаратное обеспечение обрабатывает переполнение.
  2. Более поздние компиляторы, таким образом, делая предположения, такие как сумма двух положительных целых чисел, также должны быть положительными для генерации оптимизированного машинного кода.

Итак, если компиляторы Rust выполняют ту же оптимизацию, что и компиляторы C/C++, в отношении целых чисел со знаком, почему в Rustonomicon говорится:

Несмотря ни на что, Safe Rust не может вызывать неопределенное поведение.

Или даже если компиляторы Rust не выполняют такую ​​оптимизацию, программисты на Rust все равно не ожидают увидеть целое число со знаком, оборачивающееся вокруг. Разве это нельзя назвать "неопределенным поведением"?

1 ответ

Решение

В: Итак, если компиляторы Rust выполняют ту же оптимизацию, что и компиляторы C/C++, в отношении целых чисел со знаком

Ржавчины нет. Поскольку, как вы заметили, он не может выполнять эти оптимизации, поскольку целочисленные переполнения хорошо определены.

Для добавления в режиме выпуска Rust выдаст следующую инструкцию LLVM (вы можете проверить это на Playground):

add i32 %b, %a

С другой стороны, clang выдаст следующую инструкцию LLVM (вы можете проверить через clang -S -emit-llvm add.c):

add nsw i32 %6, %8

Разница в том nsw(без подписанного переноса) флаг. Как указано в справочнике LLVM оadd:

Если сумма имеет переполнение без знака, возвращается математический результат по модулю 2n, где n - разрядность результата.

Поскольку целые числа LLVM используют представление с дополнением до двух, эта инструкция подходит как для знаковых, так и для беззнаковых целых чисел.

nuw а также nswозначает "Без подписи" и "Без подписи" соответственно. Еслиnuw и / или nsw ключевые слова присутствуют, результирующее значение добавления является опасным значением, если происходит беззнаковое и / или подписанное переполнение, соответственно.

Значение яда - это то, что приводит к неопределенному поведению. Если флаги отсутствуют, результат хорошо определяется как упаковка дополнения до 2.


В: Или даже если компиляторы Rust не выполняют такую ​​оптимизацию, программисты на Rust все равно не ожидают, что они увидят целое число со знаком. Разве это нельзя назвать "неопределенным поведением"?

"Неопределенное поведение", используемое в этом контексте, имеет очень специфическое значение, которое отличается от интуитивного английского значения этих двух слов. UB здесь конкретно означает, что компилятор может предполагать, что переполнение никогда не произойдет, и что если переполнение произойдет, любое поведение программы разрешено. Это не то, что указывает Rust.

Тем не менее, целое число от переполнения с помощью арифметических операторов будет считаться ошибкой в Rust. Это потому, что, как вы сказали, этого обычно не ожидают. Если вы намеренно хотите использовать упаковку, существуют такие методы, как i32::wrapping_add.


Некоторые дополнительные ресурсы:

Другие вопросы по тегам