¿Es la optimización de movimiento condicional contra el estándar C?
assembly language-lawyer (2)
El estándar C describe una máquina abstracta que ejecuta código C. Un compilador es libre de realizar cualquier optimización siempre y cuando esa abstracción no sea violada, es decir, un programa conforme no puede distinguir la diferencia.
¿Es una optimización común utilizar el movimiento condicional (ensamblaje cmov
) para optimizar la expresión condicional ?:
En C. Sin embargo, el estándar C dice:
Se evalúa el primer operando; hay un punto de secuencia entre su evaluación y la evaluación del segundo o tercer operando (el que se evalúe). El segundo operando se evalúa solo si el primero se compara con 0; el tercer operando se evalúa solo si el primero se compara igual a 0; el resultado es el valor del segundo o tercer operando (el que se evalúe), convertido al tipo descrito a continuación.110)
Por ejemplo, el siguiente código C
#include <stdio.h>
int main() {
int a, b;
scanf("%d %d", &a, &b);
int c= a > b ? a + 1 : 2 + b;
printf("%d", c);
return 0;
}
generará código asm relacionado optimizado de la siguiente manera:
call __isoc99_scanf
movl (%rsp), %esi
movl 4(%rsp), %ecx
movl $1, %edi
leal 2(%rcx), %eax
leal 1(%rsi), %edx
cmpl %ecx, %esi
movl $.LC1, %esi
cmovle %eax, %edx
xorl %eax, %eax
call __printf_chk
De acuerdo con el estándar, la expresión condicional solo tendrá una rama evaluada. Pero aquí se evalúan ambas ramas, lo que está en contra de la semántica del estándar. ¿Es esta optimización contra el estándar C? ¿O es que muchas optimizaciones del compilador tienen algo inconsistente con el estándar de lenguaje?
La optimización es legal, debido a la "regla de if-if" , es decir, C11 5.1.2.3p6 .
Solo se requiere una implementación conforme para producir un programa que cuando se ejecuta produzca el mismo comportamiento observable que la ejecución del programa utilizando la semántica abstracta que habría producido . El resto de la norma solo describe estas semánticas abstractas .
Lo que el programa compilado hace internamente no importa en absoluto, lo único que importa es que cuando el programa finaliza no tiene ningún otro comportamiento observable, excepto leer a
y b
e imprimir el valor de a + 1
o b + 2
dependiendo de cuál uno a
o b
es mayor, a menos que ocurra algo que cause que el comportamiento sea indefinido. (La entrada incorrecta hace que a, b sea no inicializado y, por lo tanto, acceda a indefinido; el error de rango y el desbordamiento firmado también pueden ocurrir). Si ocurre un comportamiento indefinido, entonces todas las apuestas están desactivadas.
Dado que los accesos a variables volátiles deben evaluarse estrictamente de acuerdo con la semántica abstracta, puede deshacerse del movimiento condicional utilizando volatile
aquí:
#include <stdio.h>
int main() {
volatile int a, b;
scanf("%d %d", &a, &b);
int c = a > b ? a + 1 : 2 + b;
printf("%d", c);
return 0;
}
compila a
call __isoc99_scanf@PLT
movl (%rsp), %edx
movl 4(%rsp), %eax
cmpl %eax, %edx
jg .L7
movl 4(%rsp), %edx
addl $2, %edx
.L3:
leaq .LC1(%rip), %rsi
xorl %eax, %eax
movl $1, %edi
call __printf_chk@PLT
[...]
.L7:
.cfi_restore_state
movl (%rsp), %edx
addl $1, %edx
jmp .L3
por mi GCC Ubuntu 7.2.0-8ubuntu3.2