Puntero contra matriz en C, diferencia no trivial

Pensé que realmente entendí esto, y volver a leer el estándar (ISO 9899: 1990) simplemente confirma mi comprensión obviamente errónea, así que ahora pregunto aquí.

El siguiente progtwig falla:

#include  #include  typedef struct { int array[3]; } type1_t; typedef struct { int *ptr; } type2_t; type1_t my_test = { {1, 2, 3} }; int main(int argc, char *argv[]) { (void)argc; (void)argv; type1_t *type1_p = &my_test; type2_t *type2_p = (type2_t *) &my_test; printf("offsetof(type1_t, array) = %lu\n", offsetof(type1_t, array)); // 0 printf("my_test.array[0] = %d\n", my_test.array[0]); printf("type1_p->array[0] = %d\n", type1_p->array[0]); printf("type2_p->ptr[0] = %d\n", type2_p->ptr[0]); // this line crashes return 0; } 

Comparando las expresiones my_test.array[0] y type2_p->ptr[0] según mi interpretación del estándar:

6.3.2.1 Suscripción de matriz

“La definición del operador de subíndice [] es que E1 [E2] es idéntico a (* ((E1) + (E2)))”.

Aplicando esto da:

 my_test.array[0] (*((E1)+(E2))) (*((my_test.array)+(0))) (*(my_test.array+0)) (*(my_test.array)) (*my_test.array) *my_test.array type2_p->ptr[0] *((E1)+(E2))) (*((type2_p->ptr)+(0))) (*(type2_p->ptr+0)) (*(type2_p->ptr)) (*type2_p->ptr) *type2_p->ptr 

type2_p->ptr tiene el tipo “puntero a int” y el valor es la dirección de inicio de my_test . *type2_p->ptr por *type2_p->ptr tanto evalúa a un objeto entero cuyo almacenamiento está en la misma dirección que my_test tiene.

Promover:

6.2.2.1 Lvalues, arrays y designadores de funciones

“Excepto cuando es el operando del operador sizeof o el operador unario, …, un lvalue que tiene tipo array of type se convierte a una expresión con un tipo de pointer to type que apunta al elemento inicial del objeto array y no es un lvalue “.

my_test.array tiene el tipo “array of int” y es como se describió anteriormente convertido a “puntero a int” con la dirección del primer elemento como valor. *my_test.array por *my_test.array tanto evalúa a un objeto entero cuyo almacenamiento está en la misma dirección que el primer elemento en la matriz.

Y finalmente

6.5.2.1 Estructura y especificadores de unión

Un puntero a un objeto de estructura, convenientemente convertido, apunta a su miembro inicial …, y viceversa. Puede haber un relleno sin nombre dentro de un objeto de estructura, pero no al principio, según sea necesario para lograr la alineación adecuada.

Dado que el primer miembro de type1_t es la matriz, la dirección de inicio de eso y todo el objeto type1_t es el mismo que el descrito anteriormente. Por lo tanto, entendí que *type2_p->ptr evalúa un entero cuyo almacenamiento está en la misma dirección que el primer elemento de la matriz y, por lo tanto, es idéntico a *my_test.array .

Pero este no puede ser el caso, porque el progtwig se bloquea consistentemente en Solaris, Cygwin y Linux con las versiones 2.95.3, 3.4.4 y 4.3.2 de gcc, por lo que cualquier problema ambiental está completamente fuera de cuestión.

¿Dónde está mi razonamiento equivocado / qué no entiendo? ¿Cómo declaro type2_t para hacer ptr point al primer miembro de la matriz?

Una matriz es un tipo de almacenamiento. Sintácticamente, se usa como un puntero, pero físicamente, no hay una variable “puntero” en esa estructura, solo los tres enteros. Por otro lado, el puntero int es un tipo de datos real almacenado en la estructura. Por lo tanto, cuando realiza el reparto, probablemente esté * haciendo que ptr tome el valor del primer elemento de la matriz, a saber, 1.

* No estoy seguro de que esto sea realmente un comportamiento definido, pero así es como funcionará en la mayoría de los sistemas comunes al menos.

Por favor, perdóneme si omito algo en su análisis. Pero creo que el error fundamental en todo eso es esta suposición errónea

type2_p-> ptr tiene el tipo “puntero a int” y el valor es la dirección de inicio de my_test.

No hay nada que lo haga tener ese valor. Más bien, es muy probable que señale un lugar para

 0x00000001 

Porque lo que haces es interpretar los bytes que componen esa matriz entera como un puntero. Luego le agrega algo y subíndice.

Además, dudo mucho que tu conversión a la otra estructura sea realmente válida (como en, garantizado que funciona). Puede lanzar y luego leer una secuencia inicial común de cualquiera de las estructuras si ambas son miembros de una unión. Pero no están en tu ejemplo. También puede convertir un puntero al primer miembro. Por ejemplo:

 typedef struct { int array[3]; } type1_t; type1_t f = { { 1, 2, 3 } }; int main(void) { int (*arrayp)[3] = (int(*)[3])&f; (*arrayp)[0] = 3; assert(f.array[0] == 3); return 0; } 

¿Dónde está mi razonamiento equivocado / qué no entiendo?

type_1::array (no estrictamente syntax C) no es un int * ; es un int [3] .

¿Cómo declaro type2_t para hacer ptr point al primer miembro de la matriz?

 typedef struct { int ptr[]; } type2_t; 

Eso declara un miembro de matriz flexible. Del Estándar C (6.7.2.1 párrafo 16):

Sin embargo, cuando a. (o ->) el operador tiene un operando izquierdo que es (un puntero) una estructura con un miembro de matriz flexible y el operando derecho nombra ese miembro, se comporta como si ese miembro se reemplazara con la matriz más larga (con el mismo tipo de elemento) ) que no haría la estructura más grande que el objeto al que se accede; el desplazamiento de la matriz seguirá siendo el del miembro de matriz flexible, incluso si esto difiriera del de la matriz de reemplazo.

Es decir, puede alias type1_t::array correctamente.

Tiene que ser un comportamiento definido. Piénselo en términos de memoria.

Para simplificar, supongamos que my_test está en la dirección 0x80000000.

 type1_p == 0x80000000 &type1_p->my_array[0] == 0x80000000 // my_array[0] == 1 &type1_p->my_array[1] == 0x80000004 // my_array[1] == 2 &type1_p->my_array[2] == 0x80000008 // my_array[2] == 3 

Cuando lo lanzas a type2_t,

 type2_p == 0x80000000 &type2_p->ptr == 0x8000000 // type2_p->ptr == 1 type2_p->ptr[0] == *(type2_p->ptr) == *1 

Para hacer lo que desee, deberá crear una estructura secundaria y asignar la dirección de la matriz a ptr (por ejemplo, type2_p-> ptr = type1_p-> my_array) o declarar ptr como una matriz (o una matriz de longitud variable, por ejemplo, int ptr []).

Alternativamente, puede acceder a los elementos de una manera fea: (& type2_p-> ptr) [0] , (& type2_p-> ptr) [1] . Sin embargo, tenga cuidado aquí ya que (& type2_p-> ptr) [0] en realidad será un int * , no un int . En las plataformas de 64 bits, por ejemplo, (& type2_p-> ptr) [0] en realidad será 0x100000002 (4294967298).