这里是一个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的not optimized well:
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进行模式匹配的内建函数。
如果您无法访问体系结构的溢出标志,那么我想出的最好的方法就是在unsigned
中执行操作。只需考虑一下此处的所有位算术,因为我们只对最高位感兴趣,最高位是当解释为有符号值时的符号位。
((所有模数符号错误,我没有仔细检查过,但我希望这个想法很清楚)
#include <stdbool.h>
bool overadd(int a[static 1], int b) {
unsigned A = a[0];
unsigned B = b;
// This computation will be done anyhow
unsigned AB = A + B;
// See if the sign bits are equal
unsigned AeB = ~(A^B);
unsigned AuAB = (A^AB);
// The function result according to these should be:
//
// AeB \ AuAB | false | true
//------------+-------+------
// false | false | false
// true | false | true
//
// So the expression to compute from the sign bits is (AeB & AuAB)
// This is INT_MAX
unsigned M = -1U/2;
bool ret = (AeB & AuAB) > M;
if (!ret) a[0] += b;
return ret;
}
如果找到不包含UB的附加版本,例如原子版本,则汇编器甚至没有分支(但带有锁定前缀)
#include <stdbool.h>
#include <stdatomic.h>
bool overadd(_Atomic(int) a[static 1], int b) {
unsigned A = a[0];
atomic_fetch_add_explicit(a, b, memory_order_relaxed);
unsigned B = b;
// This computation will be done anyhow
unsigned AB = A + B;
// See if the sign bits are equal
unsigned AeB = ~(A^B);
unsigned AuAB = (A^AB);
// The function result according to these should be:
//
// AeB \ AuAB | false | true
//------------+-------+------
// false | false | false
// true | false | true
//
// So the expression to compute from the sign bits is (AeB & AuAB)
// This is INT_MAX
unsigned M = -1U/2;
bool ret = (AeB & AuAB) > M;
return ret;
}
因此,如果我们进行了这样的操作,但更多地“放松了,这可以进一步改善这种情况。
我能想到的最好的版本是:
int safe_add(int *value, int delta) {
long long t = *value + (long long)delta;
if (t != ((int)t))
return -1;
*value = (int) t;
return 0;
}
产生:
safe_add(int*, int):
movslq %esi, %rax
movslq (%rdi), %rsi
addq %rax, %rsi
movslq %esi, %rax
cmpq %rsi, %rax
jne .L3
movl %eax, (%rdi)
xorl %eax, %eax
ret
.L3:
movl $-1, %eax
ret
我可以通过假设(并断言)两个补码表示法来使编译器使用符号标志。请注意,以下代码仅处理正整数加法,但可以扩展。
int safe_add(int* lhs, int rhs) {
_Static_assert(-1 == ~0, "integers are not two's complement");
unsigned value = *lhs;
value += rhs;
if ((int) value < 0) return -1;
*lhs = value;
return 0;
}
clang和GCC上的[This yields:
safe_add:
add esi, DWORD PTR [rdi]
js .L3
mov DWORD PTR [rdi], esi
xor eax, eax
ret
.L3:
mov eax, -1
ret