Me confundí un poco cuando leí esta macro: #define g_once_init_enter (ubicación) que se define en la biblioteca glib.

#define g_once_init_enter(location)                            \
  (G_GNUC_EXTENSION({                                          \
  G_STATIC_ASSERT(sizeof *(location) == sizeof(gpointer));     \
  (void) (0 ? (gpointer) *(location) : 0);                     \
  (!g_atomic_pointer_get (location) &&                         \
  g_once_init_enter (location));                               \
}))

Cuál es el efecto de esta línea: (void) (0 ? (gpointer) *(location) : 0); y la última línea es g_once_init_enter(location) de nuevo, ¿es un bucle muerto? Gracias por su respuesta.

1
Kevin.WU 23 feb. 2018 a las 10:03

2 respuestas

La mejor respuesta

Esto me parece una verificación de tipo implícita.

La macro obviamente espera que se le pase la dirección de un gpointer (lo que sea), pero las macros no tienen tipos de argumentos, por lo que no puede decir eso.

En cambio, afirma que sizeof *(location) es lo mismo que sizeof(gpointer), lo que verifica que location se puede desreferenciar y que lo que está apuntando tiene el tamaño correcto (de lo contrario, la línea G_STATIC_ASSERT no sería no compilar).

Luego se asegura de que lo que sea que esté apuntando (location) se pueda convertir a gpointer, compilando el reparto (gpointer) *(location). Esta línea no tiene ningún otro efecto (y el elenco nunca se alcanza en tiempo de ejecución); solo está ahí para hacer que el compilador se queje si la transmisión no es válida de alguna manera.

Las macros no se expanden de forma recursiva. La última línea, g_once_init_enter(location), se deja como está, por lo que debe haber una función g_once_init_enter real en algún lugar que se pueda llamar aquí.

1
melpomene 23 feb. 2018 a las 07:18

Este es un código muy feo. Linea por linea:

  • G_GNUC_EXTENSION es aparentemente lo mismo que __extension__, que es un truco sucio para ocultar el código incorrecto debajo de la alfombra. Le dice al compilador gcc que trate el código no estándar como si fuera C.
  • G_STATIC_ASSERT(sizeof *(location) == sizeof(gpointer)); es una afirmación estática anterior a C11 que se expandirá a algún tipo de error críptico del compilador en caso de que los datos señalados no sean del mismo tamaño que el tipo gpointer. Es un intento de obtener un poco de seguridad de tipos.
  • (void) (0 ? (gpointer) *(location) : 0); es un intento fallido de lograr la seguridad de los tipos, escribiendo una línea que nunca se ejecuta, pero que contiene un molde. En particular, casi cualquier tipo en C se puede convertir a casi cualquier otro tipo sin generar un error de compilador, por lo que esta línea no logra mucho en absoluto.
  • El resto son dos llamadas a funciones en las que g_once_init_enter solo se ejecuta en caso de que g_atomic_pointer_get devuelva 0 / NULL.

En general, la macro intenta lograr la seguridad de tipos en C, pero no lo logra. Sobre todo porque es una misión imposible, al menos antes de C11.

En particular, es cuestionable tener un tipo llamado gpointer, ya que significa que o el tipo no es un puntero en absoluto, o es un puntero oculto detrás de una typedef. En cualquier caso, muy mala práctica.

En C moderno, probablemente podría reemplazar todo este lío con esto:

#define g_once_init_enter(location) \
  _Generic(*(location), gpointer :  \
  !g_atomic_pointer_get (location) && g_once_init_enter (location) )
2
Lundin 23 feb. 2018 a las 07:57