Aqui está uma função C que adiciona um int
a outro, falhando se o estouro acontecer:
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;
}
Infelizmente, ele não é otimizado bem pelo GCC ou pelo 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
Esta versão com __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;
}
}
é otimizado melhor :
safe_add(int*, int):
xorl %eax, %eax
addl (%rdi), %esi
seto %al
jo .L5
movl %esi, (%rdi)
ret
.L5:
movl $-1, %eax
ret
mas estou curioso para saber se existe uma maneira, sem usar os built-in, que correspondam aos padrões do GCC ou do Clang.
c
gcc
optimization
clang
integer-overflow
Tavian Barnes
fonte
fonte
Respostas:
A melhor que eu criei, se você não tiver acesso ao sinalizador de estouro da arquitetura, é fazer as coisas
unsigned
. Basta pensar em toda aritmética de bits aqui, pois estamos interessados apenas no bit mais alto, que é o bit de sinal quando interpretado como valores assinados.(Todos esses erros de sinal de módulo, eu não verifiquei isso completamente, mas espero que a idéia seja clara)
Se você encontrar uma versão da adição livre de UB, como uma atômica, o assembler fica sem ramificação (mas com um prefixo de bloqueio)
Portanto, se tivéssemos essa operação, mas ainda mais "relaxada", isso poderia melhorar ainda mais a situação.
Take3: se usarmos um "elenco" especial do resultado não assinado para o assinado, isso agora é gratuito:
fonte
unsigned
. Mas isso depende do fato de o tipo não assinado não ter apenas o bit de sinal mascarado. (Ambos agora estão garantidos no C2x, ou seja, valem para todos os arcos que pudemos encontrar). Então, você não pode converter ounsigned
resultado de volta, se for maior queINT_MAX
, isso seria definido pela implementação e pode gerar um sinal.A situação com operações assinadas é muito pior do que com operações não assinadas, e vejo apenas um padrão para adição assinada, apenas para clang e somente quando um tipo mais amplo está disponível:
O clang fornece exatamente o mesmo asm de __builtin_add_overflow:
Caso contrário, a solução mais simples que consigo pensar é esta (com a interface usada pelo Jens):
gcc e clang geram asm muito semelhante . O gcc fornece isso:
Queremos calcular a soma em
unsigned
, portanto,unsigned
precisamos representar todos os valoresint
sem que nenhum deles fique junto. Para converter facilmente o resultado deunsigned
paraint
, o oposto também é útil. No geral, o complemento de dois é assumido.Em todas as plataformas populares, acho que podemos converter de
unsigned
paraint
através de uma atribuição simples comoint sum = u;
, mas, como Jens mencionou, até a mais recente variante do padrão C2x permite gerar um sinal. A próxima maneira mais natural é fazer algo assim:*(unsigned *)&sum = u;
mas as variantes não preenchidas de preenchimento aparentemente podem diferir para tipos assinados e não assinados. Portanto, o exemplo acima segue o caminho mais difícil. Felizmente, o gcc e o clang otimizam essa conversão complicada.PS As duas variantes acima não puderam ser comparadas diretamente, pois apresentam um comportamento diferente. O primeiro segue a pergunta original e não derruba o
*value
em caso de estouro. O segundo segue a resposta de Jens e sempre reprova a variável apontada pelo primeiro parâmetro, mas é sem ramificação.fonte
a melhor versão que posso apresentar é:
que produz:
fonte
int
, uma conversão de um tipo mais amplo produzirá um valor definido pela implementação ou aumentará um sinal. Todas as implementações que me interessam definem para preservar o padrão de bits que faz a coisa certa.Eu poderia fazer com que o compilador usasse o sinalizador assumindo (e afirmando) uma representação de complemento de dois sem preencher bytes. Tais implementações devem produzir o comportamento exigido na linha anotada por um comentário, embora eu não consiga encontrar uma confirmação formal positiva desse requisito no padrão (e provavelmente não exista).
Observe que o código a seguir lida apenas com adição positiva de número inteiro, mas pode ser estendido.
Isso gera clang e GCC:
fonte
_Static_assert
objetivo não é muito objetivo, porque isso é trivialmente verdadeiro em qualquer arquitetura atual e será imposto até para o C2x.INT_MAX
. Eu vou editar a postagem. Mas, novamente, não acho que esse código deva ser usado na prática de qualquer maneira.