c pointers casting

¿Por qué se afirma que esto hace referencia a la advertencia de puntero de tipo punteado específico del compilador?



pointers casting (6)

He leído various posts on Stack Overflow RE: el error de puntero de tipo puntero desreferenciado. Tengo entendido que el error es esencialmente la advertencia del compilador del peligro de acceder a un objeto a través de un puntero de un tipo diferente (aunque parece haber una excepción para char* ), que es una advertencia comprensible y razonable.

Mi pregunta es específica para el siguiente código: ¿ por qué la conversión de la dirección de un puntero a un void** califica para esta advertencia (promovida a error a través de -Werror )?

Además, este código se compila para múltiples arquitecturas de destino, solo una de las cuales genera la advertencia / error. ¿Podría esto implicar que es legítimamente una deficiencia específica de la versión del compilador?

// main.c #include <stdlib.h> typedef struct Foo { int i; } Foo; void freeFunc( void** obj ) { if ( obj && * obj ) { free( *obj ); *obj = NULL; } } int main( int argc, char* argv[] ) { Foo* f = calloc( 1, sizeof( Foo ) ); freeFunc( (void**)(&f) ); return 0; }

Si mi entendimiento, indicado anteriormente, es correcto, un void** , siendo solo un puntero, esto debería ser un lanzamiento seguro.

¿Hay alguna solución alternativa que no utilice valores que alivien esta advertencia / error específico del compilador? Es decir, entiendo eso y por qué esto resolverá el problema, pero me gustaría evitar este enfoque porque quiero aprovechar freeFunc() NULL en un out-arg previsto:

void* tmp = f; freeFunc( &tmp ); f = NULL;

Compilador de problemas (uno de uno):

user@8d63f499ed92:/build$ /usr/local/crosstool/x86-fc3/bin/i686-fc3-linux-gnu-gcc --version && /usr/local/crosstool/x86-fc3/bin/i686-fc3-linux-gnu-gcc -Wall -O2 -Werror ./main.c i686-fc3-linux-gnu-gcc (GCC) 3.4.5 Copyright (C) 2004 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ./main.c: In function `main'': ./main.c:21: warning: dereferencing type-punned pointer will break strict-aliasing rules user@8d63f499ed92:/build$

Compilador que no se queja (uno de muchos):

user@8d63f499ed92:/build$ /usr/local/crosstool/x86-rh73/bin/i686-rh73-linux-gnu-gcc --version && /usr/local/crosstool/x86-rh73/bin/i686-rh73-linux-gnu-gcc -Wall -O2 -Werror ./main.c i686-rh73-linux-gnu-gcc (GCC) 3.2.3 Copyright (C) 2002 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. user@8d63f499ed92:/build$

Actualización: descubrí que la advertencia parece generarse específicamente cuando se compila con -O2 (aún con el "compilador problemático" señalado únicamente)


Además de lo que han dicho las otras respuestas, este es un antipatrón clásico en C, y uno que debe quemarse con fuego. Aparece en:

  1. Funciones libres y nulas como la que encontró la advertencia.
  2. Funciones de asignación que evitan el idioma estándar de C de devolver void * (que no sufre este problema porque implica una conversión de valor en lugar de un punteo de tipo ), en cambio devuelve un indicador de error y almacena el resultado mediante un puntero a puntero.

Para otro ejemplo de (1), hubo un caso infame de larga data en la función av_free ffmpeg / libavcodec. Creo que finalmente se solucionó con una macro o algún otro truco, pero no estoy seguro.

Para (2), tanto cudaMalloc como posix_memalign son ejemplos.

En ninguno de los casos, la interfaz requiere de manera inherente un uso no válido, pero lo alienta encarecidamente y admite el uso correcto solo con un objeto temporal adicional de tipo void * que void * el propósito de la funcionalidad libre y nula, y hace que la asignación sea incómoda .


Aunque C fue diseñado para máquinas que usan la misma representación para todos los punteros, los autores de la Norma querían hacer que el lenguaje sea utilizable en máquinas que usan diferentes representaciones para punteros a diferentes tipos de objetos. Por lo tanto, no requerían que las máquinas que usan diferentes representaciones de puntero para diferentes tipos de punteros admitan un tipo de "puntero a cualquier tipo de puntero", aunque muchas máquinas podrían hacerlo a un costo cero.

Antes de que se redactara el Estándar, las implementaciones para plataformas que usaban la misma representación para todos los tipos de puntero permitirían unánimemente utilizar un void** , al menos con una conversión adecuada, como un "puntero a cualquier puntero". Es casi seguro que los autores de la Norma reconocieron que esto sería útil en plataformas que lo admitieran, pero como no podía ser universalmente admitido, se negaron a exigirlo. En cambio, esperaban que la implementación de calidad procesara construcciones tales como lo que la Justificación describiría como una "extensión popular", en los casos en que hacerlo tendría sentido.


Desreferenciar un puntero de tipo punteado es UB y no puede contar con lo que sucederá.

Diferentes compiladores generan diferentes advertencias, y para este propósito, diferentes versiones del mismo compilador pueden considerarse como diferentes compiladores. Esta parece una mejor explicación para la variación que ve que una dependencia de la arquitectura.

Un caso que puede ayudarlo a comprender por qué la escritura de tipos en este caso puede ser mala es que su función no funcionará en una arquitectura para la cual sizeof(Foo*) != sizeof(void*) . Eso está autorizado por el estándar, aunque no conozco ninguno para el cual esto sea cierto.

Una solución alternativa sería utilizar una macro en lugar de una función.

Tenga en cuenta que free acepta punteros nulos.


Este código no es válido según el estándar C, por lo que podría funcionar en algunos casos, pero no es necesariamente portátil.

La "regla de alias estricto" para acceder a un valor a través de un puntero que se ha convertido a un tipo de puntero diferente se encuentra en 6.5 párrafo 7:

Un objeto tendrá acceso a su valor almacenado solo mediante una expresión lvalue que tenga uno de los siguientes tipos:

  • 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 con signo o sin signo correspondiente al tipo efectivo del objeto,

  • un tipo que es el tipo con signo o sin signo correspondiente a una versión calificada del tipo efectivo del objeto,

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

  • un tipo de personaje

En su *obj = NULL; , el objeto tiene el tipo efectivo Foo* pero se accede mediante la expresión lvalue *obj con el tipo void* .

En 6.7.5.1 párrafo 2, tenemos

Para que dos tipos de puntero sean compatibles, ambos deberán estar identificados de forma idéntica y ambos deberán ser punteros a tipos compatibles.

Por lo tanto, void* y Foo* no son tipos compatibles o tipos compatibles con calificadores agregados, y ciertamente no se ajustan a ninguna de las otras opciones de la estricta regla de alias.

Aunque no es la razón técnica por la que el código no es válido, también es relevante tener en cuenta la sección 6.2.5, párrafo 26:

Un puntero a void tendrá los mismos requisitos de representación y alineación que un puntero a un tipo de carácter. Del mismo modo, los punteros a versiones calificadas o no calificadas de tipos compatibles tendrán los mismos requisitos de representación y alineación. Todos los punteros a los tipos de estructura deben tener los mismos requisitos de representación y alineación que los demás. Todos los punteros a tipos de unión tendrán los mismos requisitos de representación y alineación que los demás. Los punteros a otros tipos no necesitan tener los mismos requisitos de representación o alineación.

En cuanto a las diferencias en las advertencias, este no es un caso en el que el Estándar requiere un mensaje de diagnóstico, por lo que es solo una cuestión de cuán bueno es el compilador o su versión para detectar posibles problemas y señalarlos de manera útil. Notó que la configuración de optimización puede marcar la diferencia. Esto a menudo se debe a que se genera más información internamente acerca de cómo varias partes del programa realmente encajan en la práctica, y esa información adicional también está disponible para verificaciones de advertencia.


Un void * es tratado especialmente por el estándar C en parte porque hace referencia a un tipo incompleto. Este tratamiento no se extiende a void ** ya que apunta a un tipo completo, específicamente void * .

Las estrictas reglas de alias dicen que no puede convertir un puntero de un tipo en un puntero de otro tipo y, posteriormente, desreferenciar ese puntero porque hacerlo significa reinterpretar los bytes de un tipo como otro. La única excepción es cuando se convierte a un tipo de carácter que le permite leer la representación de un objeto.

Puede sortear esta limitación utilizando una macro similar a una función en lugar de una función:

#define freeFunc(obj) (free(obj), (obj) = NULL)

A lo que puedes llamar así:

freeFunc(f);

Sin embargo, esto tiene una limitación, porque la macro anterior evaluará obj dos veces. Si está utilizando GCC, esto puede evitarse con algunas extensiones, específicamente el tipo de palabras clave y expresiones de declaración:

#define freeFunc(obj) ({ typeof (&(obj)) ptr = &(obj); free(*ptr); *ptr = NULL; })


Un valor de tipo void** es un puntero a un objeto de tipo void* . Un objeto de tipo Foo* no es un objeto de tipo void* .

Hay una conversión implícita entre valores de tipo Foo* y void* . Esta conversión puede cambiar la representación del valor. Del mismo modo, puede escribir int n = 3; double x = n; int n = 3; double x = n; y esto tiene el comportamiento bien definido de establecer x en el valor 3.0 , pero double *p = (double*)&n; tiene un comportamiento indefinido (y en la práctica no establecerá p en un "puntero a 3.0 " en ninguna arquitectura común).

Las arquitecturas donde diferentes tipos de punteros a objetos tienen diferentes representaciones son raras hoy en día, pero están permitidas por el estándar C. Hay máquinas antiguas (raras) con punteros de palabras que son direcciones de una palabra en la memoria y punteros de bytes que son direcciones de una palabra junto con un desplazamiento de bytes en esta palabra; Foo* sería un puntero de palabras y void* sería un puntero de byte en tales arquitecturas. Hay máquinas (raras) con punteros gordos que contienen información no solo sobre la dirección del objeto, sino también sobre su tipo, su tamaño y sus listas de control de acceso; un puntero a un tipo definido podría tener una representación diferente de un void* que necesita información de tipo adicional en tiempo de ejecución.

Tales máquinas son raras, pero están permitidas por el estándar C. Y algunos compiladores de C aprovechan el permiso para tratar los punteros punteados como distintos para optimizar el código. El riesgo de alias de punteros es una limitación importante a la capacidad de un compilador para optimizar el código, por lo que los compiladores tienden a aprovechar tales permisos.

Un compilador es libre de decirle que está haciendo algo mal, o de hacer en silencio lo que no quería, o de hacer en silencio lo que quería. El comportamiento indefinido permite cualquiera de estos.

Puedes hacer de freefunc una macro:

#define FREE_SINGLE_REFERENCE(p) (free(p), (p) = NULL)

Esto viene con las limitaciones habituales de las macros: falta de seguridad de tipo, p se evalúa dos veces. Tenga en cuenta que esto solo le brinda la seguridad de no dejar punteros colgantes si p era el puntero único al objeto liberado.