Aquí hay una función C que agrega una int
a otra, fallando si se produce un desbordamiento:
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;
}
Lamentablemente, no está optimizado por GCC o 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 versión con __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;
}
}
está optimizado mejor :
safe_add(int*, int):
xorl %eax, %eax
addl (%rdi), %esi
seto %al
jo .L5
movl %esi, (%rdi)
ret
.L5:
movl $-1, %eax
ret
pero tengo curiosidad por saber si hay una manera sin usar incorporados que GCC o Clang coincidan con los patrones.
c
gcc
optimization
clang
integer-overflow
Tavian Barnes
fuente
fuente
Respuestas:
Lo mejor que se me ocurrió, si no tienes acceso a la bandera de desbordamiento de la arquitectura, es hacer cosas
unsigned
. Solo piense en toda la aritmética de bits aquí, ya que solo estamos interesados en el bit más alto, que es el bit de signo cuando se interpreta como valores con signo.(Todos esos errores de signo de módulo, no lo revisé a fondo, pero espero que la idea sea clara)
Si encuentra una versión de la adición que esté libre de UB, como una atómica, el ensamblador es incluso sin ramificación (pero con un prefijo de bloqueo)
Entonces, si tuviéramos tal operación, pero aún más "relajada", esto podría mejorar aún más la situación.
Take3: si usamos un "reparto" especial del resultado sin firmar al firmado, esto ahora no tiene ramificación:
fuente
unsigned
. Pero depende del hecho de que el tipo sin signo no tiene solo el bit de signo enmascarado. (Ambos están ahora garantizados en C2x, es decir, mantén todos los arcos que podamos encontrar). Entonces, no puedeunsigned
devolver el resultado si es mayor queINT_MAX
, eso sería una implementación definida y puede generar una señal.La situación con las operaciones firmadas es mucho peor que con las no firmadas, y solo veo un patrón para la adición firmada, solo para el sonido metálico y solo cuando hay un tipo más amplio disponible:
clang da exactamente el mismo asm que con __builtin_add_overflow:
De lo contrario, la solución más simple que se me ocurre es esta (con la interfaz que usó Jens):
gcc y clang generan asm muy similares . gcc da esto:
Queremos calcular la suma
unsigned
, por lounsigned
que debemos ser capaces de representar todos los valoresint
sin que ninguno se pegue. Para convertir fácilmente el resultado deunsigned
aint
, lo contrario también es útil. En general, se supone el complemento de dos.En todas las plataformas populares, creo que podemos convertir de
unsigned
aint
mediante una asignación simple comoint sum = u;
, pero, como mencionó Jens, incluso la última variante del estándar C2x le permite generar una señal. La siguiente forma más natural es hacer algo así:*(unsigned *)&sum = u;
pero las variantes de relleno sin trampa aparentemente podrían diferir para los tipos con y sin signo. Entonces, el ejemplo anterior va por el camino difícil. Afortunadamente, tanto gcc como clang optimizan esta difícil conversión.PD Las dos variantes anteriores no se pudieron comparar directamente ya que tienen un comportamiento diferente. El primero sigue la pregunta original y no golpea
*value
en caso de desbordamiento. El segundo sigue la respuesta de Jens y siempre marca la variable señalada por el primer parámetro, pero no tiene ramificaciones.fuente
La mejor versión que se me ocurre es:
que produce:
fuente
int
, una conversión de un tipo más amplio producirá un valor definido por la implementación o generará una señal. Todas las implementaciones que me interesan lo definen para preservar el patrón de bits que hace lo correcto.Podría hacer que el compilador use el indicador de signo asumiendo (y afirmando) una representación de complemento a dos sin bytes de relleno. Dichas implementaciones deberían producir el comportamiento requerido en la línea anotada por un comentario, aunque no puedo encontrar una confirmación formal positiva de este requisito en el estándar (y probablemente no haya ninguna).
Tenga en cuenta que el siguiente código solo maneja la suma de enteros positivos, pero puede ampliarse.
Esto produce tanto en clang como en GCC:
fuente
_Static_assert
objetivo no tiene mucho propósito, porque esto es trivialmente cierto en cualquier arquitectura actual, e incluso se impondrá para C2x.INT_MAX
. Editaré la publicación. Pero, de nuevo, no creo que este código deba usarse en la práctica de todos modos.