2009-03-19 7 views
11

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

Los siguientes errores en el programa:

#include <stdio.h> 
#include <stddef.h> 

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]type2_p->ptr[0] y de acuerdo con mi interpretación de la norma:

6.3.2.1 subíndices de matriz

"La definición de la El 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 tipo "puntero a int" y el valor es la dirección de inicio de my_test. *type2_p->ptr por lo tanto evalúa a un objeto entero cuyo almacenamiento está en la misma dirección que my_test tiene.

adicional:

6.2.2.1 Lvalues, matrices y designadores de función

"A menos que cuando es el operando del operador sizeof o el unario & operador, ..., An lvalue que tiene tipo array of type se convierte en una expresión con tipo pointer to type que apunta al elemento inicial del objeto de matriz y no es y lvalue. "

my_test.array tiene tipo "matriz de int" y es lo descrito antes convertido a "puntero a int" con la dirección del primer elemento como el valor. *my_test.array por lo tanto, se evalúa como 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 en su inicio , según sea necesario para lograr la alineación apropiada de .

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

Pero esto no puede ser el caso, debido a que el programa se bloquea constantemente en Solaris, Linux y cygwin con versiones de gcc 2.95.3, 3.4.4 y 4.3.2 , por lo que cualquier problema ambiental está completamente fuera de la cuestión.

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

Respuesta

10

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é * realizando una ptr sobre el valor del primer elemento de la matriz, concretamente 1.

* No estoy seguro de que esto sea realmente un comportamiento definido, pero así es como lo hará trabajar en sistemas más comunes al menos.

+0

Definitivamente es un comportamiento definido. La dirección de ptr es la misma que la dirección de my_array. my_array es en realidad un puntero a la estructura, mientras que ptr es simplemente un puntero entero dentro de una estructura. – Vitali

+2

"comportamiento definido" no significa "algo sucede", significa que "lo que sucede está definido por el estándar". El tipo de juego de palabras es un comportamiento indefinido. Si quieres ver que algo sorprendente sucede cuando escribes juego de palabras, aumenta las optimizaciones una o dos muescas en tu compilador. –

3

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

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

¿Cómo declaro que type2_t hace ptr point al primer miembro de la matriz?

typedef struct 
{  
    int ptr[]; 
} type2_t; 

que 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.

11

Por favor, perdóneme si pasé por alto algo en su análisis. Pero creo que el error fundamental en todo lo que es esta suposición equivocada

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 apunte a algún lugar

0x00000001 

Debido a lo que se hace es interpretar los bytes que componen esa matriz entera como un puntero. Luego le agrega algo y subíndice.

Además, dudo mucho que su 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; 
} 
+0

Gracias por señalar correctamente mi suposición incorrecta (el tipo de conversión 'type2_t * type2_p = (type2_t *) & my_test; '). Perdón por no aceptar su respuesta, pero seleccionaré la respuesta de Chuck, que me parece un poco precisa. – hlovdal

0

Tiene que definirse el comportamiento. 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 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 quiere, que tendría que o bien crear una estructura secundaria & 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 []).

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

Cuestiones relacionadas