2016-02-23 10 views
7

Ho una domanda riguardante le strutture in C. Quindi quando si crea una struttura, si sta essenzialmente definendo la struttura di un blocco di memoria. Pertanto, quando crei un'istanza di una struttura, stai creando un blocco di memoria tale da essere in grado di contenere un certo numero di elementi.Che cosa fa esattamente l'Operatore di Dot C Structure (Prospettiva di livello inferiore)?

Tuttavia, sono un po 'confuso su ciò che l'operatore punto sta facendo. Se ho un struct Car e hanno chiamato un membro GasMileage (che è membro int), sono in grado di ottenere il valore di GasMileage facendo qualcosa di simile,

int x = CarInstance.GasMileage; 

Tuttavia, io sono confuso su ciò che è in realtà accade con questo operatore punto. L'operatore punto agisce semplicemente come offset dall'indirizzo di base? E quanto è esattamente in grado di dedurre che è un int?

Immagino di essere curioso di sapere cosa sta succedendo dietro le quinte. Sarebbe possibile fare riferimento a GasMileage facendo qualcos'altro? Ad esempio

int *GasMileagePointer = (&carInstance + offsetInBytes(GasMileage)); 
int x = *GasMileage 

Questo è solo qualcosa che ho rapidamente inventato. Ho provato a cercare una buona spiegazione, ma nulla sembra spiegarlo oltre a considerare magico l'operatore di punti.

+0

[Come posso ottenere/impostare un membro struct per offset] (https://stackoverflow.com/questions/2043871/how-can-i-get-set-a-struct-member-by-offset). Risolve almeno la seconda parte della tua domanda. – kaylum

+0

Forse un libro sulla costruzione del compilatore sarebbe una buona lettura. – Olaf

+2

@Olaf: non è necessario comprendere la costruzione del compilatore per comprendere la semantica dell'operatore '.'. –

risposta

4

Quando si utilizza l'operatore ., il compilatore lo traduce in un offset all'interno dello struct, in base alla dimensione dei campi (e del riempimento) che lo precedono.

Ad esempio:

struct Car { 
    char model[52]; 
    int doors; 
    int GasMilage; 
}; 

Ipotizzando un int è 4 byte e nessun padding, l'offset di model è 0, l'offset di doors è 52, e l'offset di GasMilage è 56.

Quindi, se si conosce l'offset del membro, è possibile ottenere un puntatore ad esso in questo modo:

int *GasMileagePointer = (int*)((char *)&carInstance + offsetInBytes(GasMile)); 

Il cast su char * è necessario affinché l'aritmetica del puntatore passi 1 byte alla volta anziché 1 sizeof(carInstance) alla volta. Quindi il risultato deve essere convertito nel tipo di puntatore corretto, in questo caso int *

1

L'operatore punto semplicemente seleziona il membro.

Poiché il compilatore informazioni sul tipo (e conseguentemente dimensioni) dell'elemento (tutti i membri, in realtà), si conosce l'offset dell'elemento dall'inizio della struct e possono generare istruzioni appropriate. Può generare un accesso di base + offset, ma può anche accedere direttamente al membro (o persino memorizzarlo nella cache di un registro). Il compilatore ha tutte queste opzioni poiché ha tutte le informazioni necessarie al momento della compilazione.

Se non lo è, come per tipi incompleti, si otterrà un errore in fase di compilazione.

2

Sì, l'operatore punto applica semplicemente un offset dalla base della struttura e quindi accede al valore su quell'indirizzo.

int x = CarInstance.GasMileage; 

è equivalente a:

int x = *(int *)((char*)&CarInstance + offsetof(Car, GasMileage)); 

Per dell'utente con un altro tipo T, l'unica differenza è che il cast (int *) diventa (T *).

0

Quando funziona, il "." comportamento del "." l'operatore equivale a prendere l'indirizzo della struttura, indicizzarlo dall'offset del membro e convertirlo in un puntatore del tipo di membro e dereferenziarlo. Lo Standard, tuttavia, stabilisce che ci sono situazioni in cui non è garantito che funzioni. Ad esempio, dato:

struct s1 {int x,y; } 
struct s2 {int x,y; } 
void test1(struct s1 *p1, struct s2 *p2) 
{ 
    s1->x++; 
    s2->x^=1; 
    s1->x--; 
    s2->x^=1; 
} 

un compilatore può decidere che non c'è modo legittimo che P1-> x e P2-> x in grado di identificare lo stesso oggetto, quindi potrebbe riordinare il codice così come per la ++ e - operazioni su s1-> x cancel, e le operazioni^= 1 su s2-> x cancel, lasciando una funzione che non fa nulla.

Si noti che il comportamento è diverso quando si usano i sindacati, in quanto data:

union u { struct s1 v1; struct s2 v2; }; 

void test2(union u *uv) 
{ 
    u->v1.x^=1; 
    u->v2.x++; 
    u->v1.x^=1; 
    u->v2.x--; 
} 

la regola comune-iniziale-sottosequenza indica che dal U> v1 e U> v2 iniziare con i campi dello stesso tipi, un accesso a tale campo in u-> v1 è equivalente a un accesso al campo corrispondente in u-> v2. Pertanto, un compilatore non è autorizzato a risquisire le cose. D'altra parte, dato

void test1(struct s1 *p1, struct s2 *p2); 
void test3(union u *uv) 
{ 
    test1(&(u.v1), &(u.v2)); 
} 

il fatto che u.v1 e u.v2 Iniziamo con i campi corrispondenti a non premunirsi contro ipotesi di un compilatore che i puntatori non lo faranno alias.

Si noti che alcuni compilatori offrono un'opzione per forzare la generazione del codice in cui gli accessi ai membri si comportano sempre in modo equivalente alle operazioni del puntatore sopra menzionato. Per gcc, l'opzione è -fno-strict-alias. Se il codice avrà bisogno di per accedere ai membri iniziali comuni di vari tipi di strutture, omettendo che lo switch possa causare il fallimento del proprio codice in modi strani, bizzarri e imprevedibili .