Ситуация с подписанными операциями намного хуже, чем с беззнаковыми, и я вижу только один шаблон для добавления со знаком, только для clang и только когда доступен более широкий тип:
int safe_add(int *value, int delta)
{
long long result = (long long)*value + delta;
if (result > INT_MAX || result < INT_MIN) {
return -1;
} else {
*value = result;
return 0;
}
}
clang дает точно то же самое asm , что и с __builtin_add_overflow:
safe_add: # @safe_add
addl (%rdi), %esi
movl $-1, %eax
jo .LBB1_2
movl %esi, (%rdi)
xorl %eax, %eax
.LBB1_2:
retq
В противном случае самое простое решение, о котором я могу подумать, это (с использованием интерфейса, используемого Йенсом):
_Bool overadd(int a[static 1], int b)
{
// compute the unsigned sum
unsigned u = (unsigned)a[0] + b;
// convert it to signed
int sum = u <= -1u / 2 ? (int)u : -1 - (int)(-1 - u);
// see if it overflowed or not
_Bool overflowed = (b > 0) != (sum > a[0]);
// return the results
a[0] = sum;
return overflowed;
}
g cc и clang генерируют очень похожие asm . g cc дает следующее:
overadd:
movl (%rdi), %ecx
testl %esi, %esi
setg %al
leal (%rcx,%rsi), %edx
cmpl %edx, %ecx
movl %edx, (%rdi)
setl %dl
xorl %edx, %eax
ret
Мы хотим вычислить сумму в unsigned
, поэтому unsigned
должна быть в состоянии представить все значения int
без слипания между ними. Чтобы легко преобразовать результат из unsigned
в int
, полезно и обратное. В целом предполагается, что дополняют два.
На всех популярных платформах я думаю, что мы можем конвертировать из unsigned
в int
с помощью простого присваивания, такого как int sum = u;
, но, как упоминал Йенс, даже самый последний вариант Стандарт C2x позволяет поднять сигнал. Следующий наиболее естественный способ - сделать что-то подобное: *(unsigned *)&sum = u;
, но варианты дополнения без ловушек, очевидно, могут отличаться для типов со знаком и без знака. Таким образом, приведенный выше пример проходит сложный путь. К счастью, и g cc, и clang оптимизируют это сложное преобразование.
PS Два вышеописанных варианта нельзя сравнивать напрямую, поскольку они ведут себя по-разному. Первый следует исходному вопросу и не забивает *value
в случае переполнения. Второй следует за ответом от Йенса и всегда забивает переменную, на которую указывает первый параметр, но он не имеет ответвлений.