¿Tengo un error de optimización de gcc o un problema de código C?

StackOverflow https://stackoverflow.com/questions/83962

  •  01-07-2019
  •  | 
  •  

Pregunta

Pruebe el siguiente código:

#include <stdio.h>
#include <stdlib.h>
main()
{
    const char *yytext="0";
    const float f=(float)atof(yytext);
    size_t t = *((size_t*)&f);
    printf("t should be 0 but is %d\n", t);
}

Compílalo con:

gcc -O3 test.c

El BUEN resultado debería ser:

"t should be 0 but is 0"

Pero con mi gcc 4.1.3, tengo:

"t should be 0 but is -1209357172"
¿Fue útil?

Solución

Utilice el indicador del compilador -fno-strict-aliasing.

Con el alias estricto habilitado, como está por defecto para al menos -O3, en la línea:

size_t t = *((size_t*)&f);

el compilador asume que size_t* NO apunta a la misma área de memoria que float*.Hasta donde yo sé, este es un comportamiento que cumple con los estándares (el cumplimiento de reglas estrictas de alias en el estándar ANSI comienza alrededor de gcc-4, como señaló Thomas Kammeyer).

Si no recuerdo mal, puedes usar una conversión intermedia a char* para solucionar este problema.(el compilador asume que char* puede alias cualquier cosa)

En otras palabras, prueba esto (no puedo probarlo yo mismo ahora pero creo que funcionará):

size_t t = *((size_t*)(char*)&f);

Otros consejos

En la norma C99, esto está cubierto por la siguiente regla en 6.5-7:

Un objeto tendrá su valor almacenado accedido solo por una expresión de Lvalue que tenga uno de los siguientes tipos: 73)

  • un tipo compatible con el tipo efectivo del objeto,

  • una versión calificada de un tipo compatible con el tipo efectivo del objeto,

  • un tipo que es el tipo firmado o sin firmar correspondiente al tipo efectivo del objeto,

  • Un tipo que es el tipo firmado o sin firmar correspondiente a una versión calificada del tipo efectivo del objeto,

  • un tipo agregado o sindicato que incluye uno de los tipos mencionados entre sus miembros (incluidos, recursivamente, un miembro de una unión subaggregada o contenida), o

  • un tipo de personaje.

El último elemento es por qué funciona la conversión primero a (char*).

Esto ya no está permitido según las reglas C99 sobre alias de puntero.Los punteros de dos tipos diferentes no pueden apuntar a la misma ubicación en la memoria.Las excepciones a esta regla son los punteros void y char.

Entonces, en su código donde está transmitiendo a un puntero de size_t, el compilador puede optar por ignorar esto.Si desea obtener el valor flotante como size_t, simplemente asígnelo y el valor flotante se convertirá (truncado, no redondeado) como tal:

tamaño_t tamaño = (tamaño_t)(f);// esto funciona

Esto comúnmente se informa como un error, pero en realidad es una característica que permite a los optimizadores trabajar de manera más eficiente.

En gcc puedes desactivar esto con un modificador del compilador.Creo que -fno_strict_aliasing.

Es un código C incorrecto :-)

La parte problemática es que se accede a un objeto de tipo float convirtiéndolo en un puntero entero y desreferenciandolo.

Esto rompe la regla del alias.El compilador es libre de asumir que los punteros a diferentes tipos, como float o int, no se superponen en la memoria.Has hecho exactamente eso.

Lo que ve el compilador es que usted calcula algo, lo almacena en el flotante f y nunca más accede a él.Lo más probable es que el compilador haya eliminado parte del código y la asignación nunca se haya realizado.

La desreferenciación a través de su puntero size_t devolverá en este caso algo de basura no inicializada de la pila.

Puedes hacer dos cosas para solucionar este problema:

  1. use una unión con un flotador y un miembro size_t y realice la conversión mediante juegos de palabras.No es agradable pero funciona.

  2. use memcopy para copiar el contenido de f en su size_t.El compilador es lo suficientemente inteligente como para detectar y optimizar este caso.

¿Por qué pensarías que t debería ser 0?

O, dicho con mayor precisión, "¿Por qué pensarías que la representación binaria de un cero en coma flotante sería la misma que la representación binaria de un cero entero?"

Este es un código C incorrecto.Su elenco infringe las reglas de alias de C y el optimizador es libre de hacer cosas que infrinjan este código.Probablemente encontrará que GCC ha programado la lectura de size_t antes de la escritura de punto flotante (para ocultar la latencia de la canalización de fp).

Puede configurar el modificador -fno-strict-aliasing o utilizar una unión o un reinterpret_cast para reinterpretar el valor de forma que cumpla con los estándares.

Aparte de las alineaciones del puntero, espera ese tamaño de (tamaño_t) == tamaño de (flotante).No creo que lo sea (en Linux de 64 bits, size_t debería ser de 64 bits pero flotante de 32 bits), lo que significa que su código leerá algo no inicializado.

-O3 no se considera "sano", -O2 es generalmente el umbral superior, excepto quizás para algunas aplicaciones multimedia.

Algunas aplicaciones ni siquiera pueden llegar tan lejos y mueren si vas más allá de -O1.

Si tiene un GCC lo suficientemente nuevo (aquí estoy en 4.3), es posible que admita este comando

  gcc -c -Q -O3 --help=optimizers > /tmp/O3-opts

Si tiene cuidado, posiblemente podrá revisar esa lista y encontrar la optimización singular que está habilitando y que causa este error.

De man gcc :

  The output is sensitive to the effects of previous command line options, so for example it is possible to find out which
       optimizations are enabled at -O2 by using:

               -O2 --help=optimizers

       Alternatively you can discover which binary optimizations are enabled by -O3 by using:

               gcc -c -Q -O3 --help=optimizers > /tmp/O3-opts
               gcc -c -Q -O2 --help=optimizers > /tmp/O2-opts
               diff /tmp/O2-opts /tmp/O3-opts | grep enabled

Probé tu código con:"i686-apple-darwin9-gcc-4.0.1 (GCC) 4.0.1 (Apple Inc.construir 5465)"

y no hubo ningún problema.Producción:

t should be 0 but is 0

Entonces no hay ningún error en su código.Eso no significa que sea un buen código.Pero agregaría el retorno de la función principal y el "return 0;" Al final de la función.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top