Вот функция C, которая добавляет int
к другому, терпя неудачу, если переполнение произойдет:
int safe_add(int *value, int delta) {
if (*value >= 0) {
if (delta > INT_MAX - *value) {
return -1;
}
} else {
if (delta < INT_MIN - *value) {
return -1;
}
}
*value += delta;
return 0;
}
К сожалению, он не очень хорошо оптимизирован GCC или Clang:
safe_add(int*, int):
movl (%rdi), %eax
testl %eax, %eax
js .L2
movl $2147483647, %edx
subl %eax, %edx
cmpl %esi, %edx
jl .L6
.L4:
addl %esi, %eax
movl %eax, (%rdi)
xorl %eax, %eax
ret
.L2:
movl $-2147483648, %edx
subl %eax, %edx
cmpl %esi, %edx
jle .L4
.L6:
movl $-1, %eax
ret
Эта версия с __builtin_add_overflow()
int safe_add(int *value, int delta) {
int result;
if (__builtin_add_overflow(*value, delta, &result)) {
return -1;
} else {
*value = result;
return 0;
}
}
будет оптимизирован лучше :
safe_add(int*, int):
xorl %eax, %eax
addl (%rdi), %esi
seto %al
jo .L5
movl %esi, (%rdi)
ret
.L5:
movl $-1, %eax
ret
но мне любопытно, есть ли способ без использования встроенных функций, которые будут сопоставлены с шаблоном GCC или Clang.
c
gcc
optimization
clang
integer-overflow
Тавиан Барнс
источник
источник
Ответы:
Лучшее, что я придумал, если у вас нет доступа к флагу переполнения архитектуры, - это что-то делать
unsigned
. Просто подумайте обо всей арифметике битов, поскольку нас интересует только старший бит, который является знаковым битом, когда интерпретируется как знаковые значения.(Все эти ошибки по модулю, я не проверял это подробно, но я надеюсь, что идея ясна)
Если вы обнаружите версию дополнения, свободную от UB, например атомарную, ассемблер будет даже без ветвления (но с префиксом блокировки)
Так что если бы у нас была такая операция, но еще более «расслабленная», это могло бы еще больше улучшить ситуацию
Take3: если мы используем специальное «приведение» от неподписанного результата к подписанному, это теперь без веток:
источник
unsigned
. Но это зависит от того факта, что беззнаковый тип имеет не только замаскированный бит знака. (Оба теперь гарантированы в C2x, то есть сохраняются для всех дуг, которые мы могли найти). Тогда вы не можете привестиunsigned
результат обратно, если он больше чемINT_MAX
, это будет определено реализацией и может вызвать сигнал.Ситуация с подписанными операциями намного хуже, чем с неподписанными, и я вижу только один шаблон для добавления со знаком, только для clang и только когда доступен более широкий тип:
clang выдает точно такой же asm, как и в __builtin_add_overflow:
В противном случае самое простое решение, которое я могу придумать, - это (с интерфейсом, который использовал Дженс):
gcc и clang генерируют очень похожий ассм . GCC дает это:
Мы хотим вычислить сумму в
unsigned
, поэтомуunsigned
должны иметь возможность представлять все значенияint
без слипания между ними. Чтобы легко преобразовать результат изunsigned
вint
, полезно и обратное. В целом, два дополнения предполагается.Я думаю, что на всех популярных платформах мы можем
unsigned
выполнить преобразование сint
помощью простого назначения, например,int sum = u;
но, как упоминал Дженс, даже самый последний вариант стандарта C2x позволяет ему подавать сигнал. Следующий наиболее естественный способ - сделать что-то подобное:*(unsigned *)&sum = u;
но варианты дополнения без ловушек, очевидно, могут отличаться для типов со знаком и без знака. Таким образом, приведенный выше пример проходит сложный путь. К счастью, и gcc, и clang оптимизируют это сложное преобразование.PS Два приведенных выше варианта нельзя сравнивать напрямую, так как они ведут себя по-разному. Первый следует первоначальному вопросу и не забивает
*value
в случае переполнения. Второй следует за ответом Йенса и всегда перебивает переменную, на которую указывает первый параметр, но он не имеет ответвлений.источник
лучшая версия, которую я могу придумать:
который производит:
источник
int
, приведение от более широкого типа либо выдаст значение, определенное реализацией, либо вызовет сигнал. Все реализации, которые мне нужны, определяют его, чтобы сохранить битовый шаблон, который делает правильные вещи.Я мог бы заставить компилятор использовать флаг знака, предполагая (и утверждая) представление дополнения до двух без заполнения байтов. Такие реализации должны приводить к требуемому поведению в строке, аннотированной комментарием, хотя я не могу найти положительного формального подтверждения этого требования в стандарте (и, вероятно, его нет).
Обратите внимание, что следующий код обрабатывает только положительное целочисленное сложение, но может быть расширено.
Это дает и Clang и GCC:
источник
_Static_assert
задача не слишком важна, потому что это тривиально верно для любой современной архитектуры и даже будет навязываться для C2x.INT_MAX
. Я буду редактировать пост. Но опять же, я не думаю, что этот код должен использоваться на практике в любом случае.