El siguiente código entra en un bucle infinito en GCC:
#include <iostream>
using namespace std;
int main(){
int i = 0x10000000;
int c = 0;
do{
c++;
i += i;
cout << i << endl;
}while (i > 0);
cout << c << endl;
return 0;
}
Así que aquí está el trato: el desbordamiento de entero firmado es un comportamiento técnicamente indefinido. Pero GCC en x86 implementa aritmética de enteros utilizando instrucciones de enteros x86, que se ajustan al desbordamiento.
Por lo tanto, hubiera esperado que se ajustara al desbordamiento, a pesar del hecho de que es un comportamiento indefinido. Pero claramente ese no es el caso. ¿Así que ... qué me perdí?
Compilé esto usando:
~/Desktop$ g++ main.cpp -O2
Salida de GCC:
~/Desktop$ ./a.out
536870912
1073741824
-2147483648
0
0
0
... (infinite loop)
Con las optimizaciones deshabilitadas, no hay un bucle infinito y la salida es correcta. Visual Studio también compila correctamente esto y da el siguiente resultado:
Salida correcta:
~/Desktop$ g++ main.cpp
~/Desktop$ ./a.out
536870912
1073741824
-2147483648
3
Aquí hay algunas otras variaciones:
i *= 2; // Also fails and goes into infinite loop.
i <<= 1; // This seems okay. It does not enter infinite loop.
Aquí está toda la información relevante de la versión:
~/Desktop$ g++ -v
Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/lib/x86_64-linux-gnu/gcc/x86_64-linux-gnu/4.5.2/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ..
...
Thread model: posix
gcc version 4.5.2 (Ubuntu/Linaro 4.5.2-8ubuntu4)
~/Desktop$
Entonces la pregunta es: ¿Es esto un error en GCC? ¿O entendí mal algo acerca de cómo GCC maneja la aritmética de enteros?
* También etiqueto este C, porque supongo que este error se reproducirá en C. (aún no lo he verificado).
EDITAR:
Aquí está el ensamblaje del bucle: (si lo reconocí correctamente)
.L5:
addl %ebp, %ebp
movl $_ZSt4cout, %edi
movl %ebp, %esi
.cfi_offset 3, -40
call _ZNSolsEi
movq %rax, %rbx
movq (%rax), %rax
movq -24(%rax), %rax
movq 240(%rbx,%rax), %r13
testq %r13, %r13
je .L10
cmpb $0, 56(%r13)
je .L3
movzbl 67(%r13), %eax
.L4:
movsbl %al, %esi
movq %rbx, %rdi
addl $1, %r12d
call _ZNSo3putEc
movq %rax, %rdi
call _ZNSo5flushEv
cmpl $3, %r12d
jne .L5
gcc -S
.Respuestas:
Cuando el estándar dice que es un comportamiento indefinido, lo dice en serio . Cualquier cosa puede suceder. "Cualquier cosa" incluye "generalmente enteros, pero en ocasiones ocurren cosas extrañas".
Sí, en las CPU x86, los enteros generalmente se ajustan a la forma esperada. Esta es una de esas excepciones. El compilador supone que no provocará un comportamiento indefinido y optimiza la prueba de bucle. Si realmente quiere envolvente, pasar
-fwrapv
a lag++
ogcc
al compilar; esto le proporciona una semántica de desbordamiento bien definida (dos complementos), pero puede afectar el rendimiento.fuente
-fwrapv
. Gracias por señalar esto.Es simple: el comportamiento indefinido, especialmente con la optimización (
-O2
) activada, significa que cualquier cosa puede suceder.Su código se comporta como (usted) esperaba sin el
-O2
interruptor.Por cierto, funciona bastante bien con icl y tcc, pero no puedes confiar en cosas así ...
De acuerdo con esto , la optimización de gcc en realidad explota el desbordamiento de enteros con signo. Esto significaría que el "error" es por diseño.
fuente
for (j = i; j < i + 10; ++j) ++k;
, simplemente se configurarák = 10
, ya que esto siempre será cierto si no se produce un desbordamiento firmado.Lo importante a tener en cuenta aquí es que los programas C ++ están escritos para la máquina abstracta C ++ (que generalmente se emula a través de instrucciones de hardware). El hecho de que esté compilando para x86 es totalmente irrelevante para el hecho de que esto tiene un comportamiento indefinido.
El compilador es libre de usar la existencia de un comportamiento indefinido para mejorar sus optimizaciones (al eliminar un condicional de un bucle, como en este ejemplo). No hay mapeo garantizado, ni siquiera útil, entre construcciones de nivel C ++ y construcciones de código de máquina de nivel x86, aparte del requisito de que el código de máquina, cuando se ejecute, produzca el resultado exigido por la máquina abstracta de C ++.
fuente
// el desbordamiento no está definido.
Con -fwrapv es correcto. -fwrapv
fuente
Por favor, gente, el comportamiento indefinido es exactamente eso, indefinido . Significa que cualquier cosa podría suceder. En la práctica (como en este caso), el compilador es libre de asumir que noser llamado, y hacer lo que le plazca si eso puede hacer que el código sea más rápido / pequeño. Lo que sucede con el código que no debería ejecutarse es una incógnita. Dependerá del código circundante (dependiendo de eso, el compilador podría generar un código diferente), variables / constantes utilizadas, indicadores del compilador, ... Ah, y el compilador podría actualizarse y escribir el mismo código de manera diferente, o podría obtenga otro compilador con una vista diferente sobre la generación de código. O simplemente obtenga una máquina diferente, incluso otro modelo en la misma línea de arquitectura podría tener su propio comportamiento indefinido (busque códigos de operación indefinidos, algunos programadores emprendedores descubrieron que algunas de esas primeras máquinas a veces hacían cosas útiles ...) . No hay"el compilador da un comportamiento definido en el comportamiento indefinido". Hay áreas que están definidas por la implementación, y debería poder contar con que el compilador se comporte de manera consistente.
fuente
Incluso si un compilador especificara que el desbordamiento de enteros debe considerarse una forma "no crítica" de Comportamiento indefinido (como se define en el Anexo L), el resultado de un desbordamiento de enteros debería, en ausencia de una promesa de plataforma específica de un comportamiento más específico, ser como mínimo considerado como un "valor parcialmente indeterminado". Bajo tales reglas, agregar 1073741824 + 1073741824 podría considerarse arbitrariamente que produce 2147483648 o -2147483648 o cualquier otro valor que sea congruente con 2147483648 mod 4294967296, y los valores obtenidos por las adiciones podrían considerarse arbitrariamente como cualquier valor que sea congruente con 0 mod 4294967296.
Las reglas que permiten que el desbordamiento produzca "valores parcialmente indeterminados" estarían suficientemente bien definidas para cumplir con la letra y el espíritu del Anexo L, pero no evitarían que un compilador hiciera las mismas inferencias generalmente útiles que se justificaría si los desbordamientos no estuvieran restringidos Comportamiento indefinido. Evitaría que un compilador realice algunas "optimizaciones" falsas cuyo efecto principal en muchos casos es requerir que los programadores agreguen desorden adicional al código cuyo único propósito es evitar tales "optimizaciones"; si eso sería algo bueno o no depende del punto de vista de uno.
fuente