youtuber - gcc, alias estrictos e historias de terror
story time paranormal (6)
El siguiente código devuelve 10, bajo gcc 4.4.4. ¿Hay algún problema con el método de unión o gcc 4.4.4?
int main()
{
int v = 10;
union vv {
int v;
short q;
} *s = (union vv *)&v;
s->v = 1;
return v;
}
En gcc-strict-aliasing-and-casting-through-a-union , pregunté si alguien había tenido problemas con los consejos sindicales a través de punteros. Hasta ahora, la respuesta parece ser No.
Esta pregunta es más amplia: ¿Tiene alguna historia de terror sobre gcc y aliasing estricto?
Antecedentes: Citando la respuesta de AndreyT en c99-strict-aliasing-rules-in-c-gcc :
"Las reglas de alias estrictas están enraizadas en partes del estándar que estaban presentes en C y C ++ desde el comienzo de los tiempos [estandarizados]. La cláusula que prohíbe el acceso al objeto de un tipo a través de un lvalor de otro tipo está presente en C89 / 90 (6.3 ) así como también en C ++ 98 (3.10 / 15) ... Es solo que no todos los compiladores querían (u osaron) hacer cumplir o confiar en él ".
Bueno, ahora gcc se atreve a hacerlo, con su -fstrict-aliasing
. Y esto ha causado algunos problemas. Véase, por ejemplo, el excelente artículo http://davmac.wordpress.com/2009/10/ sobre un error de Mysql, y la discusión igualmente excelente en http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html .
Algunos otros enlaces menos relevantes:
- performance-impact-of-fno-strict-aliasing
- strict-aliasing
- when-is-char-safe-for-strict-pointer-aliasing
- how-to-detect-strict-aliasing-at-compile-time
Entonces, para repetir, ¿tienes una historia de terror tuya? Los problemas no indicados por -Wstrict-aliasing
serían, por supuesto, preferidos. Y otros compiladores de C también son bienvenidos.
Añadido el 2 de junio : el primer enlace en la respuesta de Michael Burr, que de hecho califica como una historia de terror, es quizás un poco anticuado (desde 2003). Hice una prueba rápida, pero aparentemente el problema desapareció.
Fuente:
#include <string.h>
struct iw_event { /* dummy! */
int len;
};
char *iwe_stream_add_event(
char *stream, /* Stream of events */
char *ends, /* End of stream */
struct iw_event *iwe, /* Payload */
int event_len) /* Real size of payload */
{
/* Check if it''s possible */
if ((stream + event_len) < ends) {
iwe->len = event_len;
memcpy(stream, (char *) iwe, event_len);
stream += event_len;
}
return stream;
}
La queja específica es:
Algunos usuarios se han quejado de que cuando el código [arriba] se compila sin el alias -fno-strict-aliasing, el orden de la escritura y memcpy se invierte (lo que significa que se falsifica una len ficticia en la secuencia).
Código compilado, usando gcc 4.3.4 en CYGWIN wih -O3 (corrija si estoy equivocado, mi ensamblador está un poco oxidado):
_iwe_stream_add_event:
pushl %ebp
movl %esp, %ebp
pushl %ebx
subl $20, %esp
movl 8(%ebp), %eax # stream --> %eax
movl 20(%ebp), %edx # event_len --> %edx
leal (%eax,%edx), %ebx # sum --> %ebx
cmpl 12(%ebp), %ebx # compare sum with ends
jae L2
movl 16(%ebp), %ecx # iwe --> %ecx
movl %edx, (%ecx) # event_len --> iwe->len (!!)
movl %edx, 8(%esp) # event_len --> stack
movl %ecx, 4(%esp) # iwe --> stack
movl %eax, (%esp) # stream --> stack
call _memcpy
movl %ebx, %eax # sum --> retval
L2:
addl $20, %esp
popl %ebx
leave
ret
Y para el segundo enlace en la respuesta de Michael,
*(unsigned short *)&a = 4;
gcc por lo general (¿siempre?) dará una advertencia. Pero creo que una solución válida para esto (para gcc ) es usar:
#define CAST(type, x) (((union {typeof(x) src; type dst;}*)&(x))->dst)
// ...
CAST(unsigned short, a) = 4;
Le pregunté si esto está bien en gcc-strict-aliasing-and-casting-through-a-union , pero hasta ahora nadie está en desacuerdo.
La regla de la secuencia inicial común de C solía interpretarse como la posibilidad de escribir una función que podría funcionar en la parte delantera de una amplia variedad de tipos de estructuras, siempre que comiencen con elementos de tipos coincidentes. Debajo de C99, la regla se cambió para que solo se aplicara si los tipos de estructura implicados fueran miembros de la misma unión cuya declaración completa fuera visible en el punto de uso.
Los autores de gcc insisten en que el idioma en cuestión solo es aplicable si los accesos se realizan a través del tipo de unión, a pesar de los hechos que:
No habría ninguna razón para especificar que la declaración completa debe estar visible si los accesos deben realizarse a través del tipo de unión.
Aunque la regla CIS se describió en términos de uniones, su principal utilidad radicaba en lo que implicaba sobre la forma en que se estructuraron y accedieron las estructuras. Si S1 y S2 fueran estructuras que compartían un CIS, no habría manera de que una función que aceptara un puntero a un S1 y un S2 desde una fuente externa pudiera cumplir con las reglas CIS de C89 sin permitir que el mismo comportamiento sea útil con punteros a estructuras que no estaban realmente dentro de un objeto de unión; por lo tanto, la especificación del soporte de CIS para las estructuras habría sido redundante dado que ya estaba especificado para las uniones.
No tengo ninguna historia de terror, pero aquí hay algunas citas de Linus Torvalds (lo siento si ya están en una de las referencias enlazadas en la pregunta):
http://lkml.org/lkml/2003/2/26/158 :
Fecha Mié, 26 Feb 2003 09:22:15 -0800 Asunto Re: Compilación inválida sin -fno-strict-aliasing De Jean Tourrilhes <>
El miércoles, 26 de febrero de 2003 a las 04:38:10 PM +0100, Horst von Brand escribió:
Jean Tourrilhes <> dijo:
Parece un error de compilación para mí ... Algunos usuarios se han quejado de que cuando se compila el siguiente código sin el alias -fno-strict-aliasing, el orden de la escritura y memcpy se invierte (lo que significa que se falsifica una len falsa) en la corriente). Código (desde linux / include / net / iw_handler.h):
static inline char * iwe_stream_add_event(char * stream, /* Stream of events */ char * ends, /* End of stream */ struct iw_event *iwe, /* Payload */ int event_len) /* Real size of payload */ { /* Check if it''s possible */ if((stream + event_len) < ends) { iwe->len = event_len; memcpy(stream, (char *) iwe, event_len); stream += event_len; } return stream; }
En mi humilde opinión, el compilador debe tener suficiente contexto para saber que el reordenamiento es peligroso. Cualquier sugerencia para hacer este código simple más a prueba de balas es bienvenido.
El compilador puede suponer que char * stream y struct iw_event * iwe apuntan a áreas de memoria separadas, debido al estricto aliasing.
Lo cual es cierto y cuál no es el problema del que me quejo.
(Nótese retrospectivamente: este código es memcpy
, pero la implementación de Linux de memcpy
fue una macro que se memcpy
en long *
para copiar en fragmentos más grandes. Con una memcpy
correctamente definida, gcc -fstrict-aliasing
no puede romper este código. Pero significa que necesita asm en línea para definir un kernel memcpy
si su compilador no sabe cómo convertir un bucle de byte-copia en un asm eficiente, que fue el caso de gcc antes de gcc7)
Y el comentario de Linus Torvald sobre lo anterior:
Jean Tourrilhes escribió:>
Parece un error de compilación para mí ...
¿Por qué crees que el kernel usa "-fno-strict-aliasing"?
La gente de gcc está más interesada en tratar de descubrir qué se puede permitir con las especificaciones c99 que en hacer que las cosas realmente funcionen . El código de alias en particular ni siquiera vale la pena, simplemente no es posible decirle a gcc cuando algunas cosas pueden alias.
Algunos usuarios se han quejado de que cuando se compila el siguiente código sin el alias -fno-strict-aliasing, el orden de escritura y memcpy se invierte (lo que significa que se falsifica una len ficticia en la secuencia).
El "problema" es que ponemos en línea el memcpy (), en cuyo momento a gcc no le importará el hecho de que pueda alias, por lo que solo reordenarán todo y alegarán que es culpa nuestra. A pesar de que no hay una manera sensata para nosotros incluso decirle a gcc al respecto.
Hace algunos años intenté encontrarme de una manera sensata, y los desarrolladores de gcc realmente no se preocupaban por el mundo real en esta área. Me sorprendería si eso hubiera cambiado, a juzgar por las respuestas que ya he visto.
No voy a molestarme en luchar contra eso.
Linus
http://www.mail-archive.com/[email protected]/msg01647.html :
El alias basado en tipo es estúpido . Es tan increíblemente estúpido que ni siquiera es gracioso. Esta roto. Y gcc tomó la noción quebrantada, y lo hizo aún más al convertirlo en una cosa "por la letra de la ley" que no tiene sentido.
...
Sé con certeza que gcc volvería a ordenar los accesos de escritura que estaban claramente (estáticamente) en la misma dirección. Gcc de repente pensaría que
unsigned long a; a = 5; *(unsigned short *)&a = 4;
podría reordenarse para establecerlo en 4 primero (porque claramente no alias - leyendo el estándar), y luego porque ahora la asignación de ''a = 5'' era posterior, ¡la asignación de 4 podría eludirse por completo! Y si alguien se queja de que el compilador está loco, la gente del compilador diría "nyaah, nyaah, los estándares que la gente dice que podemos hacer esto", sin ninguna introspección para preguntar si hizo algún SENTIDO.
SWIG genera código que depende de que el alias estricto esté desactivado, lo que puede causar todo tipo de problemas .
SWIGEXPORT jlong JNICALL Java_com_mylibJNI_make_1mystruct_1_1SWIG_12(
JNIEnv *jenv, jclass jcls, jint jarg1, jint jarg2) {
jlong jresult = 0 ;
int arg1 ;
int arg2 ;
my_struct_t *result = 0 ;
(void)jenv;
(void)jcls;
arg1 = (int)jarg1;
arg2 = (int)jarg2;
result = (my_struct_t *)make_my_struct(arg1,arg2);
*(my_struct_t **)&jresult = result; /* <<<< horror*/
return jresult;
}
aquí esta el mio:
http://forum.openscad.org/CGAL-3-6-1-causing-errors-but-CGAL-3-6-0-OK-tt2050.html
causó que ciertas formas en un programa CAD se dibujaran incorrectamente. gracias a Dios por los líderes del proyecto que trabajan en la creación de un conjunto de pruebas de regresión.
el error solo se manifestó en ciertas plataformas, con versiones anteriores de GCC y versiones anteriores de ciertas bibliotecas. y luego solo con -O2 activado. -fno-strict-aliasing lo resolvió.
Arrays gcc, aliasing y 2-D de longitud variable: El siguiente código de ejemplo copia una matriz de 2x2:
#include <stdio.h>
static void copy(int n, int a[][n], int b[][n]) {
int i, j;
for (i = 0; i < 2; i++) // ''n'' not used in this example
for (j = 0; j < 2; j++) // ''n'' hard-coded to 2 for simplicity
b[i][j] = a[i][j];
}
int main(int argc, char *argv[]) {
int a[2][2] = {{1, 2},{3, 4}};
int b[2][2];
copy(2, a, b);
printf("%d %d %d %d/n", b[0][0], b[0][1], b[1][0], b[1][1]);
return 0;
}
Con gcc 4.1.2 en CentOS, obtengo:
$ gcc -O1 test.c && a.out
1 2 3 4
$ gcc -O2 test.c && a.out
10235717 -1075970308 -1075970456 11452404 (random)
No sé si esto es generalmente conocido, y no sé si esto es un error o una característica. No puedo duplicar el problema con gcc 4.3.4 en Cygwin , por lo que puede haberse solucionado. Algunas soluciones alternativas
- Use
__attribute__((noinline))
para copiar (). - Use el
-fno-strict-aliasing
gcc-fno-strict-aliasing
. - Cambie el tercer parámetro de copy () de
b[][n]
ab[][2]
. - No use
-O2
o-O2
.
Notas adicionales:
- Esta es una respuesta, después de un año y un día, a mi propia pregunta (y estoy un poco sorprendido de que solo haya otras dos respuestas).
- Perdí varias horas con esto en mi código real, un filtro de Kalman. Cambios aparentemente pequeños tendrían efectos drásticos, tal vez debido a la modificación automática de gcc (esto es una suposición, todavía no estoy seguro). Pero probablemente no califica como una historia de terror .
- Sí, sé que no escribirías
copy()
esta manera. (Y, aparte, me sorprendió un poco ver que gcc no desenrollaba el doble circuito). - No hay interruptores de advertencia de gcc, incluyen
-Wstrict-aliasing=
, hicieron algo aquí. - Las matrices de longitud variable 1-D parecen estar bien.
Actualización : Lo anterior no responde realmente la pregunta del OP, ya que él (es decir, yo) estaba preguntando sobre casos en los que el alias estricto ''legítimamente'' rompió su código, mientras que lo anterior simplemente parece ser un error del compilador de variedad de jardín.
Informé a GCC Bugzilla , pero no estaban interesados en el antiguo 4.1.2, aunque (creo) es la clave del RHEL5 de mil millones de dólares. No ocurre en 4.2.4 arriba.
Y tengo un ejemplo ligeramente más simple de un error similar, con una sola matriz. El código:
static void zero(int n, int a[][n]) {
int i, j;
for (i = 0; i < n; i++)
for (j = 0; j < n; j++)
a[i][j] = 0;
}
int main(void) {
int a[2][2] = {{1, 2},{3, 4}};
zero(2, a);
printf("%d/n", a[1][1]);
return 0;
}
produce los resultados:
gcc -O1 test.c && a.out
0
gcc -O1 -fstrict-aliasing test.c && a.out
4
Parece que es la combinación -fstrict-aliasing
con -finline
que causa el error.