Cuando se diseña una interfaz C, es común dejar entrar a la interfaz pública ( .h ) solo lo que el programa de usuario necesita saber.
Por lo tanto, por ejemplo, los componentes internos de las estructuras deberían permanecer ocultos si el programa de usuario no necesita conocerlos. De hecho, esta es una buena práctica, ya que el contenido y el comportamiento de la estructura podrían cambiar en el futuro, sin afectar la interfaz.
Una excelente manera de lograr ese objetivo es usar tipos incompletos.
typedef struct foo opaqueType;
Ahora se puede construir una interfaz que use solo punteros a opaqueType , sin que el programa de usuario necesite conocer el funcionamiento interno de struct foo .
Pero a veces, puede ser necesario asignar dicha estructura de forma estática, normalmente en la pila, por problemas de rendimiento y fragmentación de la memoria. Obviamente, con la construcción anterior, opaqueType está incompleto, por lo que se desconoce su tamaño, por lo que no se puede asignar de forma estática.
Una solución es asignar un "tipo de shell", como:
typedef struct { int faketable[8]; } opaqueType;
La construcción anterior impone un tamaño y una alineación, pero no va más allá en la descripción de lo que realmente contiene la estructura. Por lo tanto, coincide con el objetivo de mantener el tipo "opaco".
Funciona principalmente. Pero en una circunstancia (GCC 4.4), el compilador se queja de que rompe el alias estricto y genera errores binarios.
Ahora, he leído un montón de cosas sobre el alias estricto, así que supongo que ahora entiendo lo que significa.
La pregunta es: ¿hay alguna manera de definir un tipo opaco que, sin embargo, pueda asignarse en la pila y sin romper la estricta regla de alias?
Tenga en cuenta que he intentado el método de unión descrito en este excelente artículo , pero aún genera la misma advertencia.
Tenga en cuenta también que visual, clang y gcc 4.6 y posteriores no se quejan y funcionan bien con esta construcción.
[Editar] Complemento de información:
Según las pruebas, el problema solo ocurre en las siguientes circunstancias:
.c . Aparentemente no importa si son parte del mismo sindicato. No importa si el tipo público contiene char .Finalmente, mi objetivo es C90. Tal vez C99 si realmente no hay otra opción.
Puede forzar la alineación con max_align_t y puede evitar los problemas estrictos de creación de alias utilizando una matriz de char , ya que se permite explícitamente que los caracteres char alias de cualquier otro tipo.
Algo del estilo de:
#include <stdint.h> struct opaque { union { max_align_t a; char b[32]; // or whatever size you need. } u; }; Si desea admitir un compilador que no tenga max_align_t , o si conoce los requisitos de alineación del tipo real, puede usar cualquier otro tipo para a miembro de la unión.
ACTUALIZACIÓN : si está apuntando a C11, también puede usar alignas() :
#include <stdint.h> #include <stdalign.h> struct opaque { alignas(max_align_t) char b[32]; }; Por supuesto, puede reemplazar max_align_t con cualquier tipo que considere apropiado. O incluso un número entero.
ACTUALIZACIÓN #2 :
Entonces, el uso de este tipo en la biblioteca sería algo así como:
void public_function(struct opaque *po) { struct private *pp = (struct private *)po->b; //use pp->... } De esta manera, dado que está haciendo un juego de palabras con un puntero a char , no está rompiendo las estrictas reglas de alias.
Lo que desea es algún tipo de equivalente del control de acceso private de C ++ en C. Como sabe, no existe tal equivalente. El enfoque que das es aproximadamente lo que yo haría. Sin embargo, haría que opaqueType opaco para los componentes internos que implementan el tipo, por lo que me vería obligado a convertirlo en el tipo real dentro de los componentes internos. El lanzamiento forzado no debe generar la advertencia que mencionas.
Aunque es engorroso de usar, puede definir una interfaz que proporcione memoria "asignada en pila" a un tipo opaco sin exponer una estructura de tamaño. La idea es que el código de implementación esté a cargo de la asignación de la pila, y el usuario pasa una función de devolución de llamada para obtener un puntero al tipo asignado.
typedef struct opaqueType_raii_callback opqaueType_raii_callback; struct opaqueType_raii_callback { void (*func)(opqaueType_raii_callback *, opqaueType *); }; extern void opaqueType_raii (opaqueType_raii_callback *); extern void opaqueType_raii_v (opaqueType_raii_callback *, size_t); void opaqueType_raii (opaqueType_raii_callback *cb) { opaqueType_raii_v(cb, 1); } void opqaueType_raii_v (opaqueType_raii_callback *cb, size_t n) { opaqueType x[n]; cb->func(cb, x); }Las definiciones anteriores parecen un poco esotéricas, pero es la forma en que normalmente implemento una interfaz de devolución de llamada.
struct foo_callback_data { opaqueType_raii_callback cb; int my_data; /* other data ... */ }; void foo_callback_function (opaqueType_raii_callback *cb, opaqueType *x) { struct foo_callback_data *data = (void *)cb; /* use x ... */ } void foo () { struct foo_callback_data data; data.cb.func = foo_callback_function; opaqueType_raii(&data.cb); }Para mí, esto parece ser algo que simplemente no debería hacerse.
El objetivo de tener un puntero opaco es ocultar los detalles de implementación. El tipo y la alineación de la memoria donde se asigna la estructura real, o si la biblioteca administra datos adicionales más allá de lo señalado, también son detalles de implementación.
Por supuesto, no es que no pudieras documentar que una u otra cosa era posible, pero el lenguaje C usa este enfoque (aliasing estricto), que solo puedes modificar más o menos con la respuesta de Rodrigo (usando max_align_t ). Por regla general, no puede saber por la interfaz qué tipo de restricciones impondría el compilador en particular sobre la estructura real dentro de la implementación (para algunos microcontroladores esotéricos, incluso el tipo de memoria puede importar), por lo que no creo que esto pueda hacerse de manera confiable de una manera verdaderamente multiplataforma.