c assembly language-lawyer compiler-optimization ternary-operator

¿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