2009-03-19 4 views
11

Ho pensato di averlo davvero capito e la rilettura dello standard (ISO 9899: 1990) conferma semplicemente la mia comprensione ovviamente sbagliata, quindi ora chiedo qui.Puntatore contro array in C, differenza non banale

Il programma va in crash seguenti:

#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; 
} 

Confrontando le espressioni my_test.array[0] e type2_p->ptr[0] secondo la mia interpretazione dello standard:

6.3.2.1 Array indicizzazione di

"La definizione del operatore operator [] è che E1 [E2] è identico a (* ((E1) + (E2))). "

Applicando questo dà:

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 è di tipo "puntatore a int" e il valore è l'indirizzo iniziale my_test. *type2_p->ptr pertanto valuta un oggetto intero la cui memoria è allo stesso indirizzo di my_test.

Ulteriore:

6.2.2.1 lvalue, array e designatori funzione

"Tranne quando è l'operando del operatore sizeof o unario & operatore, ... delle lvalue che ha tipo array of type viene convertito in un'espressione con tipo pointer to type che punta all'elemento iniziale dell'array e non è un lvalue. "

my_test.array ha tipo "array di int" ed è come descritto sopra convertito in "puntatore a int" con l'indirizzo del primo elemento come valore. *my_test.array pertanto valuta un oggetto intero la cui memoria si trova allo stesso indirizzo del primo elemento dell'array.

Infine

6.5.2.1 Struttura e sindacali specificatori

Un puntatore a un oggetto struttura, opportunamente convertito, punti alla sua membro iniziale ..., e viceversa. È possibile che all'interno di un oggetto struttura ci sia un pad senza nome, ma non al suo inizio , se necessario per ottenere l'allineamento appropriato .

Poiché il primo membro della type1_t la matrice, l'indirizzo iniziale di questo e l'intero type1_t oggetto è lo stesso come sopra descritto. La mia comprensione è stata quindi che *type2_p->ptr valuta a un numero intero la cui archiviazione è nello stesso indirizzo del primo elemento nell'array e pertanto è identico a *my_test.array.

Ma questo non può essere il caso, in quanto il programma si blocca costantemente su Solaris, cygwin e Linux con le versioni gcc 2.95.3, 3.4.4 e 4.3.2 , in modo che qualsiasi questione ambientale è completamente fuori questione.

Dove è il mio ragionamento sbagliato/cosa non capisco? Come posso dichiarare type2_t per rendere ptr point al primo membro dell'array?

risposta

10

Una matrice è un tipo di archiviazione. Sintatticamente, è usato come un puntatore, ma fisicamente, non c'è una variabile "pointer" in quella struttura - solo i tre interi.D'altra parte, il puntatore int è un tipo di dati effettivo memorizzato nella struttura. Pertanto, quando esegui il cast, probabilmente stai * facendo ptr assumere il valore del primo elemento dell'array, ovvero 1.

* Non sono sicuro che questo sia in realtà un comportamento definito, ma è così che sarà lavorare almeno sui sistemi più comuni.

+0

È sicuramente un comportamento definito. L'indirizzo di ptr è lo stesso dell'indirizzo di my_array. my_array è in realtà un puntatore nella struttura, mentre ptr è semplicemente un puntatore intero all'interno di una struttura. – Vitali

+2

"comportamento definito" non significa "succede qualcosa", significa "il qualcosa che accade è definito dallo standard". Digitare la punteggiatura è un comportamento indefinito. Se vuoi vedere qualcosa di sorprendente accadere quando scrivi un gioco di parole, avvia le ottimizzazioni di una tacca o due sul tuo compilatore. –

3

Dove è il mio ragionamento sbagliato/cosa non capisco?

type_1::array (non strettamente sintassi C) non è un int *; è un int [3].

Come si dichiara type2_t per rendere ptr point al primo membro dell'array?

typedef struct 
{  
    int ptr[]; 
} type2_t; 

che dichiara un membro di matrice flessibile. Dallo standard C (6.7.2.1 paragrafo 16):

Tuttavia, quando a. (o ->) l'operatore ha un operando di sinistra che è (un puntatore a) una struttura con un membro di matrice flessibile e l'operando destro chiama quel membro, si comporta come se quel membro fosse sostituito con l'array più lungo (con lo stesso tipo di elemento) che non renderebbe la struttura più grande dell'oggetto a cui si accede; l'offset dell'array deve rimanere quello del membro flessibile dell'array, anche se questo differirebbe da quello dell'array sostitutivo.

I.e, può alias type1_t::array correttamente.

11

Per favore perdonami se trascuro qualcosa nella tua analisi. Ma penso che l'errore fondamentale in tutto questo sia questo assunto errato

type2_p-> ptr ha tipo "puntatore a int" e il valore è l'indirizzo iniziale di my_test.

Non c'è nulla che lo renda tale valore. Piuttosto, è molto probabilmente che punti da qualche parte per

0x00000001 

Perché ciò che si fa è quello di interpretare i byte che compongono questo array intero come un puntatore. Quindi aggiungi qualcosa ad esso e pedice.

Inoltre, dubito fortemente che il tuo casting sull'altra struttura sia effettivamente valido (come in, garantito per funzionare). Puoi lanciare e poi leggere una sequenza iniziale comune di entrambe le strutture se entrambe sono membri di un sindacato. Ma non sono nel tuo esempio. Puoi anche eseguire il cast di un puntatore al primo membro. Ad esempio:

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

Grazie per aver correttamente sottolineato la mia ipotesi errata (il tipo type2_t * type2_p = (type2_t *) & my_test; 'type cast). Scusa per non aver accettato la tua risposta, ma selezionerò la risposta di Chuck che trovo un po 'precisa. – hlovdal

0

Deve essere definito il comportamento. Pensaci in termini di memoria.

Per semplicità, supponiamo che my_test sia all'indirizzo 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 

Quando lanci a type2_t,

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

di fare ciò che si vuole, si dovrà creare uno struttura secondaria & assegnare l'indirizzo della matrice PTR (ad esempio type2_p-> ptr = tipo1_p-> mia_array) o dichiarare ptr come una matrice (o una matrice di lunghezza variabile, ad esempio int ptr []).

alternativa, è possibile accedere agli elementi in maniera brutta: (& type2_p-> PTR) [0], (& type2_p-> PTR) [1]. Tuttavia, fai attenzione qui dal (& type2_p-> ptr) [0] sarà in realtà un int *, non un int . Sulle piattaforme a 64 bit, ad esempio, (& type2_p-> ptr) [0] sarà effettivamente 0x100000002 (4294967298).