vectores punteros puntero operadores lenguaje funciones estructura direccion declaracion datos con cadenas apuntadores c pointers struct c99 strict-aliasing

operadores - ¿Son todos los punteros derivados de punteros a tipos de estructura iguales?



punteros a cadenas (2)

Fondo

La suposición de que el estándar implícitamente requiere que todos los punteros sean tipos de estructura (completa, incompleta, compatible e incompatible) para tener los mismos requisitos de representación y alineación, comenzó en C89, muchos años antes de que el estándar lo requiriera explícitamente. El razonamiento detrás de esto era la compatibilidad de tipos incompletos en unidades de traducción separadas, y aunque según el comité de estándares de C, la intención original era permitir la compatibilidad de un tipo incompleto con su variación completa, las palabras reales de la norma no describían eso. Esto se modificó en el segundo corrigendum técnico de C89 y, por lo tanto, hizo que la suposición original fuera concreta.

Compatibilidad y tipos incompletos

Al leer las pautas relacionadas con la compatibilidad y tipos incompletos, gracias a Matt McNabb, encontramos una mayor comprensión de la suposición original C89.

Derivación de puntero de objeto y tipos incompletos

C99 / C11 §6.2.5 p1 :

Los tipos se dividen en tipos de objetos, tipos de funciones y tipos incompletos.

C99 / C11 §6.2.5 p20 :

Un tipo de puntero puede derivarse de un tipo de función, un tipo de objeto o un tipo incompleto, llamado el tipo al que se hace referencia.

C99 / C11 §6.2.5 p22 :

Una estructura o tipo de unión de contenido desconocido es un tipo incompleto. Se completa, para todas las declaraciones de ese tipo, al declarar la misma estructura o etiqueta de unión con su contenido de definición más adelante en el mismo ámbito.

Lo que significa que los punteros pueden derivarse tanto de tipos de objeto como de tipos incompletos. Aunque no se especifica que no se requiera completar tipos incompletos; en el pasado, el comité respondió sobre este asunto, y afirmó que la falta de una prohibición es suficiente y que no es necesaria una declaración positiva.

El siguiente puntero al puntero a ''struct never_completed'' incompleto nunca se completa:

int main(void) { struct never_completed *p; p = malloc(1024); }

[ Ejemplo de código completo ]

Tipos compatibles de unidades de traducción separadas

C99 / C11 §6.7.2.3 p4 :

Todas las declaraciones de estructura, unión o tipos enumerados que tienen el mismo alcance y usan la misma etiqueta declaran el mismo tipo.

C99 / C11 §6.2.7 p1 :

Dos tipos tienen un tipo compatible si sus tipos son los mismos. Dos tipos de estructura declarados en unidades de traducción separadas son compatibles si sus etiquetas (son) la misma etiqueta. [cita recortada] [...]

Este párrafo tiene una gran importancia, permítanme resumirlo: dos tipos de estructuras declaradas en unidades de traducción separadas son compatibles si usan la misma etiqueta. Si ambos se completan, sus miembros deben ser iguales (de acuerdo con las pautas especificadas).

Compatibilidad de punteros

C99 §6.7.5.1 p2 / C11 §6.7.6.1 p2 :

Para que dos tipos de punteros sean compatibles, ambos deben ser idénticamente calificados y ambos deben ser punteros a tipos compatibles.

Si la norma exige que dos estructuras en condiciones específicas sean compatibles en unidades de traducción separadas, ya sea incompletas o completas, significa que los indicadores derivados de estas estructuras también son compatibles.

C99 / C11 §6.2.5 p20 :

Se puede construir cualquier cantidad de tipos derivados a partir del objeto, la función y los tipos incompletos

Estos métodos de construcción de tipos derivados se pueden aplicar recursivamente.

Y debido a que la derivación del puntero es recursiva, hace que los punteros derivados de punteros a tipos de estructura compatibles sean compatibles entre sí.

Representación de tipos compatibles

C99 §6.2.5 p27 / C11 §6.2.5 p28 :

los punteros a versiones calificadas o no calificadas de tipos compatibles deberán tener los mismos requisitos de representación y alineación.

C99 / C11 §6.3 p2 :

La conversión de un valor de operando a un tipo compatible no causa ningún cambio en el valor o la representación.

C99 / C11 §6.2.5 p26 :

Las versiones calificadas o no calificadas de un tipo son tipos distintos que pertenecen a la misma categoría de tipo y tienen los mismos requisitos de representación y alineación.

Esto significa que una implementación conforme no puede tener un juicio distinto con respecto a los requisitos de representación y alineación de punteros derivados de tipos de estructura incompleta o completa, debido a la posibilidad de que una unidad de traducción separada tenga un tipo compatible, que deberá compartir el mismos requisitos de representación y alineación, y se requiere aplicar el mismo juicio distinto con una variación incompleta o completa del mismo tipo de estructura.

El siguiente puntero al puntero para incompletar ''struct complete_incomplete'':

struct complete_incomplete ** p;

Es compatible y comparte los mismos requisitos de representación y alineación que el siguiente puntero al puntero para completar ''struct complete_incomplete'':

struct complete_incomplete {int i; } **pag;

C89 relacionado

Si nos preguntamos acerca de la premisa relativa a C89, el informe de defectos #059 del Jun 93 ''cuestionó:

Ambas secciones no requieren explícitamente que un tipo incompleto finalmente deba completarse, ni permiten explícitamente que los tipos incompletos permanezcan incompletos para toda la unidad de compilación. Dado que esta característica es importante para la declaración de tipos de datos opacos verdaderos, merece una aclaración.

La consideración de estructuras referenciales mutuas definidas e implementadas en diferentes unidades de compilación hace que la idea de un tipo de datos opaco sea una extensión natural de un tipo de datos incompleto.

La respuesta del comité fue:

El Comité consideró y aprobó los tipos de datos opacos al redactar el Estándar C.

Compatibilidad versus intercambiabilidad

Hemos cubierto el aspecto relativo a los requisitos de representación y alineación de la derivación de punteros recursivos de los punteros a los tipos de estructura, ahora nos enfrentamos a un asunto que una nota al pie no normativa menciona, ''intercambiabilidad'':

C99 TC3 §6.2.5 p27 Footnote 39 / C11 §6.2.5 p28 Footnote 48 :

Los mismos requisitos de representación y alineación implican intercambiabilidad como argumentos para funciones, valores de retorno de funciones y miembros de uniones.

El estándar dice que las notas, notas al pie y ejemplos no son normativos y son "solo para información".

C99 FOREWORD p6 / C11 FOREWORD p8 :

[...] este prólogo, la introducción, notas, notas al pie y ejemplos también son solo informativos.

Es desafortunado que esta confusa nota al pie nunca haya cambiado, porque en el mejor de los casos, la nota al pie es específicamente sobre los tipos directos que se refieren a ella, por lo tanto, si las propiedades de los "requisitos de representación y alineación" están fuera del contexto de estos tipos específicos , hace que sea fácil de interpretar como una regla general para todos los tipos que comparten una representación y alineación. Si la nota debe interpretarse sin el contexto de tipos específicos, entonces es obvio que el texto normativo de la norma no lo implica, incluso sin la necesidad de debatir la interpretación del término "intercambiable".

Compatibilidad de punteros con los tipos de estructura

C99 / C11 §6.7.2.3 p4:

Todas las declaraciones de estructura, unión o tipos enumerados que tienen el mismo alcance y usan la misma etiqueta declaran el mismo tipo.

C99 / C11 §6.2.7 p1 :

Dos tipos tienen un tipo compatible si sus tipos son los mismos.

C99 §6.7.5.1 p2 / C11 §6.7.6.1 p2 :

Para que dos tipos de punteros sean compatibles, ambos deben ser idénticamente calificados y ambos deben ser punteros a tipos compatibles.

Esto establece la conclusión obvia, los diferentes tipos de estructuras son de hecho tipos diferentes, y debido a que son diferentes, son incompatibles. Por lo tanto, dos punteros a dos tipos diferentes e incompatibles son también incompatibles, independientemente de sus requisitos de representación y alineación.

Tipos efectivos

C99 / C11 §6.5 p7 :

Un objeto debe tener 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

C99 / C11 §6.5 p6 :

El tipo efectivo de un objeto para acceder a su valor almacenado es el tipo declarado del objeto, si lo hay.

Los punteros incompatibles no son "intercambiables" como argumentos para las funciones, ni como valores de retorno de las funciones. Las conversiones implícitas y los casos especiales especificados son excepciones, y estos tipos no son parte de ninguna de esas excepciones. Incluso si decidimos agregar un requisito poco realista para dicha ''intercambiabilidad'', y decimos que se requiere una conversión explícita para hacerlo aplicable, entonces el acceso al valor almacenado de un objeto con un tipo efectivo incompatible rompe las reglas de tipos efectivos. Para hacerlo realidad, necesitamos una nueva propiedad que actualmente no tiene la norma. Por lo tanto, compartir los mismos requisitos de representación y alineación, y ser convertible, simplemente no es suficiente.

Esto nos deja siendo intercambiables "como miembros de uniones", y aunque son de hecho intercambiables como miembros de la unión, no tiene un significado especial.

Interpretaciones oficiales

1. La primera interpretación ''oficial'' pertenece a un miembro del comité de estándares de C. Su interpretación de: "están destinados a implicar intercambiabilidad", es que en realidad no implica que exista tal intercambiabilidad, sino que realmente hace una sugerencia para ello.

Por más que me gustaría que se hiciera realidad, no consideraría una implementación que tomara una sugerencia de una nota al pie no normativa, sin mencionar una nota al pie de página irrazonablemente vaga, al tiempo que contradice las directrices normativas: ser una implementación conforme. Obviamente, esto hace que un programa que utilice y dependa de dicha "sugerencia" sea uno no estrictamente conforme.

2. La segunda interpretación "oficial" pertenece a un miembro / colaborador del comité de estándares de C, por su interpretación la nota al pie no introduce una sugerencia, y como el texto (normativo) del estándar no lo implica, lo considera ser un defecto en el estándar. Incluso hizo una sugerencia para cambiar las reglas de tipos efectivos para abordar este asunto.

3. La tercera interpretación ''oficial'' proviene del informe de defectos #070 de diciembre 93`. Se ha preguntado, dentro del contexto de C89, si un programa que pasa un tipo ''unsigned int'', donde se espera el tipo ''int'', como un argumento para una función con un declarador no prototipo, para introducir un comportamiento indefinido.

En C89 hay la misma nota al pie, con la misma intercambiabilidad implícita que los argumentos a las funciones, adjunta a:

C89 §3.1.2.5 p2 :

El rango de valores no negativos de un tipo entero con signo es un subrango del tipo entero sin signo correspondiente, y la representación del mismo valor en cada tipo es la misma.

El comité respondió que alientan a los implementadores a permitir que esta intercambiabilidad funcione, pero dado que no es un requisito, hace que el programa no sea estrictamente conforme.

El siguiente ejemplo de código no es estrictamente conforme . ''& s1'' y ''struct genérico **'' comparten los mismos requisitos de representación y alineación, pero no obstante son incompatibles. De acuerdo con las reglas de tipos efectivos, estamos accediendo al valor almacenado del objeto ''s1'' con un tipo efectivo incompatible, un puntero a ''struct genérico'', mientras que su tipo declarado, y por lo tanto tipo efectivo, es un puntero a ''struct s1 ''. Para superar esta limitación, podríamos haber usado los indicadores como miembros de un sindicato, pero esta convención daña el objetivo de ser genérico.

int allocate_struct(void *p, size_t s) { struct generic **p2 = p; if ((*p2 = malloc(s)) == NULL) return -1; return 0; } int main(void) { struct s1 { int i; } *s1; if (allocate_struct(&s1, sizeof *s1) != 0) return EXIT_FAILURE; }

[ Ejemplo de código completo ]

El siguiente ejemplo de código es estrictamente conforme , para superar tanto los problemas de los tipos efectivos como ser genérico, aprovechamos: 1. un puntero al vacío, 2. los requisitos de representación y alineación de todos los punteros a las estructuras, y 3. el acceso la representación del byte del puntero ''genéricamente'', mientras usa memcpy para copiar la representación, sin afectar su tipo efectivo.

int allocate_struct(void *pv, size_t s) { struct generic *pgs; if ((pgs = malloc(s)) == NULL) return -1; memcpy(pv, &pgs, sizeof pgs); return 0; } int main(void) { struct s1 { int i; } *s1; if (allocate_struct(&s1, sizeof *s1) != 0) return EXIT_FAILURE; }

[ Ejemplo de código completo ]

La conclusión

La conclusión es que una implementación conforme debe tener los mismos requisitos de representación y alineación, respectivamente, para todos los punteros derivados recursivamente a los tipos de estructura, ya sean incompletos o completos, y si son compatibles o incompatibles. Aunque es significativo si los tipos son compatibles o incompatibles, pero debido a la mera posibilidad de un tipo compatible, deben compartir las propiedades fundamentales de representación y alineación. Hubiera sido preferible si pudiéramos acceder a punteros que comparten representación y alineación directamente, pero desafortunadamente las reglas actuales de tipos efectivos no lo requieren.

La pregunta

La pregunta de si todos los punteros derivados de punteros a los tipos de estructura son los mismos, no es fácil de responder. Encuentro que es una pregunta importante para las siguientes dos razones principales.

A. La falta de un puntero al puntero a ''cualquier'' tipo incompleto o de objeto, impone una limitación en las interfaces de funciones convenientes, tales como:

int allocate(ANY_TYPE **p, size_t s); int main(void) { int *p; int r = allocate(&p, sizeof *p); }

[ Ejemplo de código completo ]

El puntero existente a ''cualquier'' incompleto o tipo de objeto se describe explícitamente como:

C99 / C11 §6.3.2.3 p1 :

Un puntero a void se puede convertir desde o hacia un puntero a cualquier tipo incompleto o de objeto. [...]

Un puntero derivado del puntero existente a ''cualquier'' tipo incompleto o de objeto, puntero a puntero a vacío, es estrictamente un puntero a puntero a vacío, y no es necesario que sea convertible con un puntero derivado de un puntero a ''cualquiera'' incompleto o tipo de objeto.


B. No es raro que los programadores utilicen convenciones basadas en supuestos que no son necesarios, relacionados con la generalización de los indicadores, a sabiendas o sin saberlo, al tiempo que dependen de su experiencia con sus implementaciones específicas. Suposiciones como ser convertible, ser representable como enteros o compartir una propiedad común: tamaño del objeto, representación o alineación.

Las palabras del estándar

De acuerdo con C99 §6.2.5 p27 / C11 §6.2.5 p28 :

[...] Todos los punteros a los tipos de estructura deben tener los mismos requisitos de representación y alineación entre sí. [...]

Seguido por C99 TC3 Footnote 39 / C11 Footnote 48 :

Los mismos requisitos de representación y alineación implican intercambiabilidad como argumentos para funciones, valores de retorno de funciones y miembros de uniones.

Aunque el estándar no dice: "Un puntero a un tipo de estructura" y se han elegido las siguientes palabras: "Todos los punteros a tipos de estructura", no especifica explícitamente si se aplica a una derivación recursiva de tales punteros. En otras ocasiones, cuando se mencionan propiedades especiales de los punteros en el estándar, no se especifica ni menciona explícitamente la derivación recursiva del puntero, lo que significa que se aplica la ''derivación del tipo'', o no lo hace, pero no se menciona explícitamente.

Y aunque la frase "Todos los apuntadores a" al referirse a los tipos se usa solo dos veces , (para estructura y tipos de unión), a diferencia de la fraseología más explícita: "Un puntero a" que se usa en todo el estándar, no podemos concluir si se aplica a una derivación recursiva de tales punteros.


Mi respuesta es no."

No hay ninguna redacción en ningún estándar de C de la que tenga conocimiento que sugiera lo contrario. El hecho de que todos los punteros a tipos de estructura tengan los mismos requisitos de representación y alineación no tiene relación con ningún tipo derivado.

Esto tiene sentido completo y cualquier otra realidad parece ser inconsistente. Considera la alternativa:

Llamemos los requisitos de alineación y representación para los punteros a los tipos de estructura "A". Supongamos que cualquier "tipo derivado recursivamente" comparte los requisitos "A".

Llamemos los requisitos de alineación y representación para los punteros a los tipos de unión "B". Supongamos que cualquier "tipo derivado recursivamente" comparte los requisitos "B".

Supongamos que "A" y "B" no son lo mismo [1]. Además, supongamos que no se pueden satisfacer al mismo tiempo. (Una representación de 4 bytes y una representación de 8 bytes, por ejemplo).

Ahora deriva un tipo de ambos:

  1. Un tipo con requisitos "A"
  2. Un tipo con requisitos "B"

Ahora tiene un tipo cuyos requisitos son imposibles de satisfacer, porque debe satisfacer "A" y "B", pero no pueden satisfacerse ambos a la vez.

Quizás piense que los tipos derivados tienen un linaje plano desde un ancestro único, pero no es así. Los tipos derivados pueden tener muchos antepasados. La definición estándar de "tipos derivados" discute esto.

[1] Si bien puede parecer irrazonable, poco probable y tonto, está permitido.