La desreferenciación del puntero con tipo de letra infringirá las reglas de alias estricto

Utilicé el siguiente fragmento de código para leer datos de archivos como parte de un progtwig más grande.

double data_read(FILE *stream,int code) { char data[8]; switch(code) { case 0x08: return (unsigned char)fgetc(stream); case 0x09: return (signed char)fgetc(stream); case 0x0b: data[1] = fgetc(stream); data[0] = fgetc(stream); return *(short*)data; case 0x0c: for(int i=3;i>=0;i--) data[i] = fgetc(stream); return *(int*)data; case 0x0d: for(int i=3;i>=0;i--) data[i] = fgetc(stream); return *(float*)data; case 0x0e: for(int i=7;i>=0;i--) data[i] = fgetc(stream); return *(double*)data; } die("data read failed"); return 1; } 

Ahora me dicen que use -O2 y recibo la siguiente advertencia de gcc: warning: dereferencing type-punned pointer will break strict-aliasing rules

Googleing encontré dos respuestas ortogonales:

  • Concluyendo: no hay que preocuparse; gcc trata de ser más obediente a la ley que la ley actual.

vs

  • Básicamente, si tiene un int * y un float * no se les permite apuntar a la misma ubicación de memoria. Si su código no respeta esto, entonces el optimizador del comstackdor probablemente rompa su código.

Al final no quiero ignorar las advertencias. ¿Qué recomendarías?

[actualización] Sustituí el ejemplo de juguete con la función real.

Parece mucho como si realmente quisieras usar fread:

 int data; fread(&data, sizeof(data), 1, stream); 

Dicho esto, si quieres seguir la ruta de lectura de caracteres, luego reinterpretándolos como un int, la forma segura de hacerlo en C (pero no en C ++) es usar una unión:

 union { char theChars[4]; int theInt; } myunion; for(int i=0; i<4; i++) myunion.theChars[i] = fgetc(stream); return myunion.theInt; 

No estoy seguro de por qué la longitud de los data en su código original es 3. Supongo que quería 4 bytes; al menos no conozco ningún sistema donde un int sea de 3 bytes.

Tenga en cuenta que tanto su código como el mío son altamente no portátiles.

Edición: si desea leer entradas de varias longitudes de un archivo, intente algo como esto:

 unsigned result=0; for(int i=0; i<4; i++) result = (result << 8) | fgetc(stream); 

(Nota: en un progtwig real, también querrá probar el valor de retorno de fgetc () contra EOF).

Esto lee un byte sin firmar de 4 bytes del archivo en formato little-endian, independientemente de cuál sea la endianidad del sistema. Debería funcionar en casi cualquier sistema donde un sin signo tenga al menos 4 bytes.

Si quieres ser neutral con respecto a los endios, no uses punteros o uniones; usar cambios de bits en su lugar.

El problema se produce porque accede a un char-array a través de un double* :

 char data[8]; ... return *(double*)data; 

Pero gcc asume que su progtwig nunca accederá a variables a través de punteros de diferente tipo. Este supuesto se denomina alias estricto y permite al comstackdor realizar algunas optimizaciones:

Si el comstackdor sabe que su *(double*) puede superponerse de ninguna manera con los data[] , se permite todo tipo de cosas, como reordenar su código en:

 return *(double*)data; for(int i=7;i>=0;i--) data[i] = fgetc(stream); 

Lo más probable es que el bucle esté optimizado y terminas con solo:

 return *(double*)data; 

Lo que deja sus datos [] sin inicializar. En este caso particular, el comstackdor podría ver que sus punteros se superponen, pero si lo hubiera declarado con char* data , podría haber dado errores.

Pero, la regla de aliasing estricto dice que un char * y void * pueden apuntar a cualquier tipo. Así que puedes reescribirlo en:

 double data; ... *(((char*)&data) + i) = fgetc(stream); ... return data; 

Las advertencias estrictas de alias son realmente importantes para entender o corregir. Provocan los tipos de errores que son imposibles de reproducir internamente porque ocurren solo en un comstackdor en particular en un sistema operativo en particular en una máquina en particular y solo en luna llena y una vez al año, etc.

Usar una unión no es lo correcto a hacer aquí. La lectura de un miembro no escrito de la unión no está definida, es decir, el comstackdor es libre de realizar optimizaciones que romperán su código (como optimizar la escritura).

Este documento resume la situación: http://dbp-consulting.com/tutorials/StrictAliasing.html

Existen varias soluciones diferentes, pero la más portátil / segura es usar memcpy (). (Las llamadas a la función pueden optimizarse, por lo que no es tan ineficiente como parece). Por ejemplo, reemplaza esto:

 return *(short*)data; 

Con este:

 short temp; memcpy(&temp, data, sizeof(temp)); return temp; 

Básicamente, puedes leer el mensaje de gcc como persona que buscas problemas, no digas que no te advertí .

Convertir una matriz de caracteres de tres bytes en un int es una de las peores cosas que he visto en mi vida. Normalmente su int tiene al menos 4 bytes. Así que para el cuarto (y quizás más si int es más amplio) obtienes datos aleatorios. Y luego lanzas todo esto a un double .

Simplemente no hagas nada de eso. El problema de alias que gcc advierte es inocente en comparación con lo que estás haciendo.

Los autores del Estándar C querían que los comstackdores del comstackdor generaran un código eficiente en circunstancias en las que sería teóricamente posible, pero poco probable que una variable global pudiera tener acceso a su valor utilizando un puntero aparentemente no relacionado. La idea no era prohibir el punteo de tipo mediante el lanzamiento y desreferenciación de un puntero en una sola expresión, sino más bien decir que dado algo como:

 int x; int foo(double *d) { x++; *d=1234; return x; } 

un comstackdor tendría derecho a asumir que la escritura en * d no afectará a x. Los autores de la Norma querían enumerar situaciones en las que una función como la anterior que recibió un puntero de una fuente desconocida tendría que asumir que podría ser un alias de un global aparentemente no relacionado, sin necesidad de que los tipos coincidan perfectamente. Desafortunadamente, si bien la justificación sugiere que los autores de la Norma pretendían describir una norma de conformidad mínima en los casos en que un comstackdor no tendría razón para creer que las cosas podrían ser un alias , la regla no exige que los comstackdores reconozcan el alias en los casos es obvio y los autores de gcc decidieron que preferirían generar el progtwig más pequeño posible mientras se ajustan al lenguaje mal escrito del Estándar, que generar código que sea realmente útil, y en lugar de reconocer el alias en los casos en que sea obvio (aunque aún puede asumir que las cosas que no parecen que van a ser alias, no) prefieren que los progtwigdores usen memcpy , por lo que requieren un comstackdor para permitir la posibilidad de que alias apuntadores de origen desconocido Casi cualquier cosa, impidiendo así la optimización.

Aparentemente, el estándar permite que sizeof (char *) sea diferente de sizeof (int *), por lo que gcc se queja cuando intentas un lanzamiento directo. void * es un poco especial porque todo se puede convertir de un lado a otro desde void *. En la práctica, no conozco muchas architectures / comstackdores donde un puntero no es siempre el mismo para todos los tipos, pero gcc tiene derecho a emitir una advertencia, incluso si es molesto.

Creo que la forma segura sería

 int i, *p = &i; char *q = (char*)&p[0]; 

o

 char *q = (char*)(void*)p; 

También puedes probar esto y ver lo que obtienes:

 char *q = reinterpret_cast(p);