Estructuras y fundición en c.

Me preguntaba:

Si tengo definiciones de estructura, por ejemplo, como esta:

struct Base { int foo; }; struct Derived { int foo; // int foo is common for both definitions char *bar; }; 

¿Puedo hacer algo como esto?

 void foobar(void *ptr) { ((struct Base *)ptr)->foo = 1; } struct Derived s; foobar(&s); 

por ejemplo, arroje el puntero de vacío a Base * para acceder a su foo cuando su tipo realmente se Derived * ?

Muchos progtwigs de C del mundo real asumen que la construcción que usted muestra es segura, y hay una interpretación del estándar C (específicamente, de la regla de “secuencia inicial común”, C99 §6.5.2.3 p5) según la cual está de acuerdo. Desafortunadamente, en los cinco años transcurridos desde que contesté esta pregunta, todos los comstackdores a los que puedo llegar fácilmente (es decir, GCC y Clang) han convergido en una interpretación diferente y más estrecha de la regla de secuencia inicial común, según la cual el constructo que usted muestra provoca comportamiento indefinido. Concretamente, experimenta con este progtwig:

 #include  #include  typedef struct A { int x; int y; } A; typedef struct B { int x; int y; float z; } B; typedef struct C { A a; float z; } C; int testAB(A *a, B *b) { b->x = 1; a->x = 2; return b->x; } int testAC(A *a, C *c) { c->ax = 1; a->x = 2; return c->ax; } int main(void) { B bee; C cee; int r; memset(&bee, 0, sizeof bee); memset(&cee, 0, sizeof cee); r = testAB((A *)&bee, &bee); printf("testAB: r=%d bee.x=%d\n", r, bee.x); r = testAC(&cee.a, &cee); printf("testAC: r=%d cee.x=%d\n", r, cee.ax); return 0; } 

Al comstackr con la optimización habilitada (y sin -fno-strict-aliasing ), tanto GCC como Clang asumirán que los dos argumentos de puntero a testAB no pueden apuntar al mismo objeto , así que obtengo una salida como

 testAB: r=1 bee.x=2 testAC: r=2 cee.x=2 

No hacen esa suposición para testAC , pero, habiendo tenido previamente la impresión de que se requería que testAB se comstackra como si sus dos argumentos pudieran apuntar al mismo objeto, ya no confío lo suficiente en mi propia comprensión de la norma para Decir si eso está o no garantizado para seguir trabajando.

Deberías hacer

 struct Base { int foo; }; struct Derived { struct Base base; char *bar; }; 

para evitar romper el aliasing estricto; es un error común pensar que C permite conversiones arbitrarias de tipos de punteros: aunque funcionará como se espera en la mayoría de las implementaciones, no es estándar.

Esto también evita cualquier incompatibilidad de alineación debido al uso de directivas pragma.

Eso funcionará en este caso particular. El campo foo en el primer miembro de ambas estructuras y golpe tiene el mismo tipo. Sin embargo, esto no es cierto en el caso general de los campos dentro de una estructura (que no son el primer miembro). Elementos como la alineación y el empaque pueden hacer esta ruptura de maneras sutiles.

Como parece que apunta a la Progtwigción Orientada a Objetos en CI puede sugerirle que eche un vistazo al siguiente enlace:

http://www.planetpdf.com/codecuts/pdfs/ooc.pdf

Entra en detalles sobre las formas de manejar los principios de OOP en ANSI C.

En casos particulares esto podría funcionar, pero en general no, debido a la alineación de la estructura.

Podría usar diferentes #pragma s para hacer (en realidad, intentar) la alineación idéntica, y luego, sí, eso funcionaría.

Si está utilizando Microsoft Visual Studio, este artículo le puede resultar útil.

Hay otra pequeña cosa que podría ser útil o relacionada con lo que está haciendo …

 #define SHARED_DATA int id; typedef union base_t { SHARED_DATA; window_t win; list_t list; button_t button; } typedef struct window_t { SHARED_DATA; int something; void* blah; } typedef struct window_t { SHARED_DATA; int size; } typedef struct button_t { SHARED_DATA; int clicked; } 

Ahora puede poner las propiedades compartidas en SHARED_DATA y manejar los diferentes tipos a través de la “superclase” empaquetada en la unión. Puede usar SHARED_DATA para almacenar solo un “identificador de clase” o almacenar un puntero. De cualquier manera, resultó útil para manejo genérico de los tipos de eventos para mí en algún momento. Espero que no me vaya demasiado fuera de tema con esto

Sé que esta es una pregunta antigua, pero en mi opinión hay más de lo que se puede decir y algunas de las otras respuestas son incorrectas.

En primer lugar, este elenco:

 (struct Base *)ptr 

… está permitido, pero solo si se cumplen los requisitos de alineación. En muchos comstackdores, sus dos estructuras tendrán los mismos requisitos de alineación, y es fácil de verificar en cualquier caso. Si supera este obstáculo, el siguiente es que el resultado del lanzamiento no está especificado en su mayoría, es decir, no hay ningún requisito en el estándar C que el puntero una vez que se haya lanzado aún se refiera al mismo objeto (solo después de devolverlo al original). tipo necesariamente lo hará).

Sin embargo, en la práctica, los comstackdores para sistemas comunes generalmente hacen que el resultado de una conversión de puntero se refiera al mismo objeto.

(Los lanzamientos de punteros se tratan en la sección 6.3.2.3 tanto del estándar C99 como del estándar C11 más reciente. Las reglas son esencialmente las mismas en ambos, creo).

Finalmente, tiene las reglas de “aliasing estricto” con las que lidiar (C99 / C11 6.5 párrafo 7); básicamente, no se le permite acceder a un objeto de un tipo a través de un puntero de otro tipo (con ciertas excepciones, que no se aplican en su ejemplo). Consulte “¿Qué es la regla de aliasing estricto?” , o para una discusión muy profunda, lea la publicación de mi blog sobre el tema.

En conclusión, no se garantiza que lo que intentas en tu código funcione. Puede garantizarse que siempre funcione con ciertos comstackdores (y con ciertas opciones de comstackción), y puede funcionar por casualidad con muchos comstackdores, pero ciertamente invoca un comportamiento indefinido de acuerdo con el estándar del lenguaje C.

Lo que podrías hacer en cambio es esto:

 *((int *)ptr) = 1; 

… Es decir, ya sabe que el primer miembro de la estructura es un int , simplemente lo convierte directamente en int , lo que evita el problema de aliasing, ya que ambos tipos de estructura contienen un int en esta dirección. Confía en conocer el diseño de la estructura que usará el comstackdor y aún confía en la semántica no estándar del lanzamiento de punteros, pero en la práctica es mucho menos probable que le dé problemas.

Lo bueno / malo de C es que puedes lanzar casi cualquier cosa, el problema es que podría no funcionar. 🙂 Sin embargo, en su caso, será *, ya que tiene dos estructuras cuyos primeros miembros son del mismo tipo; Ver este progtwig para un ejemplo. Ahora, si la struct derived tuviera un tipo diferente como primer elemento, por ejemplo, char *bar , entonces no, obtendríamos un comportamiento extraño.

* Debería calificar eso con “casi siempre”, supongo; Hay muchos comstackdores de C diferentes, por lo que algunos pueden tener comportamientos diferentes. Sin embargo, sé que funcionará en GCC.