![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
Представьте, что вам в программе нужно сложить два целых числа. Получили некий ответ, скажем 42. Верный ли это результат? А вдруг произошло переполнение, и на самом деле вышло огромное число, но не поместилось в размер переменной? Большинство языков программирования помочь не могут.
Для многих применений оно может и некритично, но что если вы делает софт для тормозной системы автомобиля. Или для импланта стимулятора сердца. От такой мелочи как переполнение кое-кто может тут же отдать богу душу.
На некоторых архитектурах целочисленное переполнение вызывает прерывание выполнения программы. В надежде, что софт умеет восстанавливаться после сбоя. Но большинство современных процессоров такого не умеют.
Не так давно в компиляторы GCC и CLANG ввели встроенные функции, чтобы отлавливать переполнение. Посмотрим, как эта штука работает.
Для многих применений оно может и некритично, но что если вы делает софт для тормозной системы автомобиля. Или для импланта стимулятора сердца. От такой мелочи как переполнение кое-кто может тут же отдать богу душу.
На некоторых архитектурах целочисленное переполнение вызывает прерывание выполнения программы. В надежде, что софт умеет восстанавливаться после сбоя. Но большинство современных процессоров такого не умеют.
Не так давно в компиляторы GCC и CLANG ввели встроенные функции, чтобы отлавливать переполнение. Посмотрим, как эта штука работает.
Сложение
Компилируем для riscv64. Конструкция превращается в машинные команды:int64_t a, b, c;
if (__builtin_add_overflow(a, b, &c)) {
// переполнение
}
Это эквивалентно Си-шному коду:add a4, a2, a3 # a4 = a + b
slti a1, a3, 0 # a1 = (b < 0)
slt a5, a4, a2 # a5 = (a + b < a)
bne a1, a5, overflow # (b < 0) != (a + b < a) ? overflow
Вполне эффективное решение.int64_t a, b, c;
c = a + b;
if ((b < 0) != (c < a)) {
// переполнение
}
Умножение
Интересно, как компилятор сделает проверку переполнения при умножении.Смотрим машинный код для riscv64.int64_t a, b, c;
if (__builtin_mul_overflow(a, b, &c)) {
// переполнение
}
На Си это выглядело бы:mul a4, a2, a3 # a4 = (a * b) bits 63:0
mulh a1, a2, a3 # a1 = (a * b) >> 64
srai a5, a4, 63 # a5 = (int64_t) (a * b) >> 63
bne a1, a5, overflow # (a * b) >> 64 != (int64_t) (a * b) >> 63 ? overflow
Неплохо придумано. К счастью, на RISC-V имеется хитрая команда MULH, которая умножает два 64-битных целых и выдаёт старшие 64 бита результата.int64_t a, b, c;
c = a * b;
if (((int128_t)a * b) >> 64 != c >> 63) {
// переполнение
}
no subject
Date: 2024-11-13 20:15 (UTC)А не работает в обратную сторону вероятно потому, что компилятор при сложении предполагает отсутствие переполнения, и поэтому оптимизатор выбрасывает весь код как несущественный.
no subject
Date: 2024-11-13 21:08 (UTC)Скорее всего это будут тонкие обертки над теми же
__builtin_*
.Именно так: переполнение знакового - UB, UB в корректных программах не бывает, значит функцию будут вызывать только с аргументами, не вызывающими переполнения, значит проверять не надо.
no subject
Date: 2024-11-13 21:16 (UTC)