¿Es legal escribir punteros de tipos diferentes de estructuras (por ejemplo, struct sockaddr * para struct sockaddr_in6 *)?

Aquí hay un progtwig que escribe entre punteros de tipo struct shape , struct rectangle y struct triangle .

 #include  #include  #include  enum { RECTANGLE, TRIANGLE, MAX }; struct shape { int type; }; struct rectangle { int type; int x; int y; }; struct triangle { int type; int x; int y; int z; }; struct shape *get_random_shape() { int type = rand() % MAX; if (type == RECTANGLE) { struct rectangle *r = malloc(sizeof (struct rectangle)); r->type = type; r->x = rand() % 10 + 1; r->y = rand() % 10 + 1; return (struct shape *) r; } else if (type == TRIANGLE) { struct triangle *t = malloc(sizeof (struct triangle)); t->type = type; t->x = rand() % 10 + 1; t->y = rand() % 10 + 1; t->z = rand() % 10 + 1; return (struct shape *) t; } else { return NULL; } } int main() { srand(time(NULL)); struct shape *s = get_random_shape(); if (s->type == RECTANGLE) { struct rectangle *r = (struct rectangle *) s; printf("perimeter of rectangle: %d\n", r->x + r->y); } else if (s->type == TRIANGLE) { struct triangle *t = (struct triangle *) s; printf("perimeter of triangle: %d\n", t->x + t->y + t->z); } else { printf("unknown shape\n"); } return 0; } 

Aquí está la salida.

 $ gcc -std=c99 -Wall -Wextra -pedantic main.c $ ./a.out perimeter of triangle: 22 $ ./a.out perimeter of triangle: 24 $ ./a.out perimeter of rectangle: 8 

Puedes ver arriba que el progtwig compiló y corrió sin advertencias. Estoy tratando de entender si es válido tipear un puntero de struct shape struct rectangle en struct rectangle y viceversa, aunque ambas estructuras sean de diferentes tamaños.

Si su respuesta es que esto no es válido, entonces considere que los libros de progtwigción de red rutinariamente encasillados entre los punteros struct sockaddr * , struct sockaddr_in * y struct sockaddr_in6 * según la familia de sockets (AF_INET vs. AF_INET6), y luego explique por qué tipo cast está bien en el caso de struct sockaddr * pero no en el caso anterior de struct shape * . Aquí hay un ejemplo de tipo cast con struct sockaddr * .

 #include  #include  #include  #include  #include  #include  int main() { struct addrinfo *ai; if (getaddrinfo("localhost", "http", NULL, &ai) != 0) { printf("error\n"); return EXIT_FAILURE; } if (ai->ai_family == AF_INET) { struct sockaddr_in *addr = (struct sockaddr_in *) ai->ai_addr; printf("IPv4 port: %d\n", addr->sin_port); } else if (ai->ai_family == AF_INET6) { struct sockaddr_in6 *addr = (struct sockaddr_in6 *) ai->ai_addr; printf("IPv6 port: %d\n", addr->sin6_port); } return 0; } 

Este código comstack y funciona bien también. Además, esta es la forma recomendada de escribir dichos progtwigs según los libros sobre progtwigción de socket.

 $ gcc -std=c99 -D_POSIX_SOURCE -Wall -Wextra -pedantic foo.c $ ./a.out IPv6 port: 20480 

¿Es legal escribir punteros de tipos diferentes de estructuras (por ejemplo, struct sockaddr * a struct sockaddr_in6 *)?

Sí. C lo prevé explícitamente:

Un puntero a un tipo de objeto se puede convertir en un puntero a un tipo de objeto diferente. Si el puntero resultante no está alineado correctamente para el tipo referenciado, el comportamiento no está definido. De lo contrario, cuando se convierta de nuevo, el resultado se comparará igual al puntero original.

(C2011, 6.3.2.3/7)

Como han señalado otras respuestas, no es el elenco el problema, sino lo que se hace con el resultado. Y eso se reduce a la estricta regla de aliasing:

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

  • un tipo compatible con el tipo efectivo del objeto,

[… más varias otras alternativas que no pueden aplicarse en este caso …]

(C2011, 6.5 / 7; énfasis añadido)

La pregunta principal, por lo tanto, es ¿cuál es el tipo efectivo del objeto al que apunta la struct sockaddr * ? Aquí es importante entender que no podemos decir de la statement de getaddrinfo() , ni de la struct addrinfo . En particular, no hay razón para suponer que el tipo efectivo es struct sockaddr .

De hecho, dado que el reparto que ha preguntado es el método estándar y previsto para acceder a los detalles de la dirección, hay razones para suponer que getaddrinfo() admite que al asegurarse de que el tipo efectivo sea el indicado por ai_family asociado código. Luego, la conversión correspondiente genera un puntero que coincide con el tipo efectivo de la información de dirección. En ese caso, no hay ningún problema inherente en el acceso a la información de la dirección a través del puntero obtenido a través de la conversión.

Observo en apoyo de lo anterior que es razonable suponer que el puntero en cuestión apunta a un objeto asignado dinámicamente. El tipo efectivo de tal objeto depende de los medios por los cuales se estableció por última vez su valor almacenado (C2011, 6.5 / 6). No solo es plausible sino que es probable que getaddrinfo() establezca ese valor de una manera que le dé el tipo de efecto deseado. Por ejemplo, el código a lo largo de las mismas líneas que su ejemplo de forma lo haría.

En última instancia, el uso previsto es la struct sockaddr * desde y hacia los punteros a las estructuras específicas de la familia de direcciones, y no hay ninguna razón para suponer que un entorno que proporciona getaddrinfo() permitiría, en la práctica, que esos comportamientos sean dudosos . Si hubiera sido necesario, POSIX (por quien se especifica la función) podría haber incorporado una regla especial que permita los lanzamientos. Pero no se necesita tal regla en este caso, aunque POSIX lo obliga a tomar esa fe.

El comstackdor diagnosticaría fielmente un error si las conversiones de tipo explícitas se eliminaran de

 struct rectangle *r = (struct rectangle *) s; 

o de

 struct triangle *t = (struct triangle *) s; 

Las conversiones de tipos explícitas, en este caso, pueden funcionar porque lo que exige la norma. En efecto, al utilizar la conversión explícita de tipos en estas dos declaraciones, está dirigiendo efectivamente al comstackdor “cállate, sé lo que estoy haciendo”.

Lo que es más interesante es por qué la función main() funciona en tiempo de ejecución, una vez que se ha convertido en un comstackdor para que el comstackdor se envíe y permita la conversión.

El código funciona porque el primer miembro de las tres struct es del mismo tipo. La dirección de una struct es igual a la dirección de su primer miembro, excepto que los tipos son diferentes (es decir, un puntero a un struct rectangle tiene un tipo diferente de un puntero a un int ). Por lo tanto (si ignoramos los diferentes tipos), la prueba s == &(s->type) será verdadera. El uso de una conversión de tipo trata con eso, así que (int *)s == &s->type .

Una vez que su código ha completado esa prueba, está realizando una conversión de tipos explícita en s . Sucede que, en la statement.

 struct rectangle *r = (struct rectangle *) s; 

que su código ha asegurado que s es en realidad la dirección de un struct rectangle (asignado dinámicamente). Por lo tanto, el uso posterior de r es válido. Similarmente en el bloque else if , con una struct triangle .

La cosa es, si cometió un error, como

 if (s->type == RECTANGLE) { struct triangle *t = (struct triangle *) s; printf("perimeter of triangle: %d\n", t->x + t->y + t->z); } 

(es decir, utilizando un struct rectangle como si fuera un struct triangle ), entonces el comstackdor seguirá permitiendo fielmente la conversión de tipos (como se explicó anteriormente). Sin embargo, el comportamiento ahora no está definido ya que s no es realmente la dirección de un struct triangle . En particular, al acceder a t->z accede a un miembro que no existe.

En el caso específico de la biblioteca de zócalos de Berkeley, el estándar POSIX garantiza que puede convertir un puntero a una struct sockaddr_storage a un puntero a cualquier tipo de zócalo y que el campo que identifica el tipo de zócalo se asignará correctamente.

Específicamente, el estándar POSIX especifica de struct sockaddr_storage :

Cuando un puntero a una estructura ss_family como un puntero a una estructura ss_family , el campo ss_family de la estructura sa_family campo sa_family de la estructura sa_family . Cuando un puntero a una estructura ss_family se ss_family como un puntero a una estructura de dirección específica del protocolo, el campo ss_family asignará a un campo de esa estructura que es de tipo sa_family_t y que identifica la familia de direcciones del protocolo.

También dice de struct sockaddr_in , “Los punteros a este tipo serán convertidos por las aplicaciones a struct sockaddr * para su uso con funciones de socket”. La interfaz de bind() , connect() y así sucesivamente solo puede funcionar si la biblioteca busca el const struct sockaddr* obtiene y determina a qué tipo de socket apunta.

Un comstackdor determinado podría necesitar magia para implementar eso, pero esta biblioteca en particular tiene que hacerlo por usted.

Su pregunta sufre de varias confusiones terminológicas.

En primer lugar, solo porque su progtwig de alguna manera “compiló y ejecutó sin advertencias” e incluso produjo el resultado que esperaba, todavía no significa que lo que está haciendo en su código sea de alguna manera “válido”.

En segundo lugar, parece que estás preguntando acerca de la validez del mismo reparto. En realidad, el elenco en sí está al lado del punto. Hay muchas cosas en C que se pueden “encasillar” entre sí. Sin embargo, el lenguaje no ofrece garantías acerca de lo que puede hacer con los resultados de dichos lanzamientos. El reparto en sí puede ser perfectamente válido, pero sus acciones adicionales aplicadas al resultado pueden ser terriblemente inválidas.

En tercer lugar, y aparentemente esto es realmente de lo que trata tu pregunta: pasar de los punteros a diferentes tipos de estructuras que comparten una subsecuencia inicial común y luego acceder a los miembros desde esa subsecuencia común a través de los punteros resultantes. No es el elenco que el problema aquí, es el acceso posterior. Y la respuesta es: no, el lenguaje no define esto como una técnica válida. El lenguaje le permite inspeccionar las subsecuencias iniciales comunes de diferentes tipos de estructuras unidas en una unión común, pero sin una unión común esto no está permitido.

En cuanto a la técnica popular con lanzamientos entre struct sockaddr * , struct sockaddr_in * y struct sockaddr_in6 * , estos son solo hacks que no tienen nada que ver con el lenguaje C. Simplemente funcionan en la práctica, pero en lo que respecta al lenguaje C, la técnica no es válida.

En realidad no está garantizado para trabajar. Se garantiza que funcionará si el comstackdor ve la statement de una unión que tiene los tres tipos; es suficiente que el comstackdor vea la statement. En ese caso, el código que accede a los elementos iniciales comunes de las estructuras está bien. Obviamente, el elemento común más importante es el miembro “tipo”.

Entonces, si ha declarado una unión de forma de estructura, rectángulo y triángulo, puede tomar un puntero que apunta a una de las tres estructuras, proyectar el puntero, acceder al campo de tipo y luego ir desde allí.

Pero esto no funciona en ningún idioma. También en C ++ debe incluir todas las variables en la clase base y declarar funciones virtuales en la clase base. En lugar de pasar a la forma y luego al rectángulo, mejor muévase a void * y que al rectángulo. Este es un paradigma orientado a objetos. El hinerhitance, el polimorphimsum y otros son exactamente lo que orienta un lenguaje a los objetos. Para trabajar con objetos en C, deberías codificar. Pero lo vale. Creo que la complejidad promedio de los progtwigs no justifica pasar a C ++. Hay una diferencia entre un Ferrari y un camión. Al menos no tienes que esforzarte mucho, C es gracioso. En tu lugar yo haría esto:

 typedef enum shape_type{ circle, rectangle, triangle, //... }S_type; typedef struct shape { S_type stype; int ar_par[4];//default allocated parameters number int* p_par; //to default it is going to contain the ar_par address //and you are going to change it case you needs more parameters. You save a malloc more int n;//count of parameters int (*get_perimeter) (struct shape *);//you can also typedef them int (*get_area)(struct shape*); }*Shape_ptr,Shape; 

que codificar tal esto

 Shape_ptr new_rectangle(int a, int b) { Shape_ptr res=malloc(sizeof(Shape)); res->stype=rectangle; res->p_par=res->ar_par;//no need to allocate anything *res->p_par[0]=a;*res->p_par[1]=b; res->n=2; res->get_perimeter=get_rectangle_perimeter; res->get_area=get_rectangle_area; } int get_rectangle_perimeter(Shape_ptr s) { return s->p_par[0]<<1 + s->p_par[1]<<1; //or multiply by two; } main() { Shape_ptr shp =get_random_shape() ; //this function is going to call new_rectangle printf ("shap area is:%d\n",(*shp->get_area)(shp); } 

Y así sucesivamente … Así es como trabajas con objetos en C. Progtwigs orientados a objetos, contiene un paradigma que en grandes progtwigs simplifica la vida del progtwigdor