Ci possono essere molti vantaggi in questo codice, ma sfortunatamente lo standard C non è stato scritto per facilitarlo. I compilatori hanno storicamente offerto garanzie comportamentali efficaci oltre a quanto richiesto dalla norma che ha permesso di scrivere tale codice in modo molto più pulito di quanto sia possibile nella norma C, ma ultimamente i compilatori hanno iniziato a revocare tali garanzie in nome dell'ottimizzazione.
In particolare, molti compilatori C hanno storicamente garantito (in base alla progettazione se non alla documentazione) che se due tipi di struttura contengono la stessa sequenza iniziale, è possibile utilizzare un puntatore a entrambi i tipi per accedere ai membri di quella sequenza comune, anche se i tipi non sono correlati, e inoltre che allo scopo di stabilire una sequenza iniziale comune tutti i puntatori alle strutture sono equivalenti. Il codice che utilizza tale comportamento può essere molto più pulito e più sicuro rispetto al codice che non lo fa, ma sfortunatamente anche se lo Standard richiede che le strutture che condividono una sequenza iniziale comune debbano essere disposte allo stesso modo, proibisce al codice di utilizzare effettivamente un puntatore di un tipo per accedere alla sequenza iniziale di un altro.
Di conseguenza, se vuoi scrivere codice orientato agli oggetti in C, dovrai decidere (e dovresti prendere presto questa decisione) di saltare attraverso molti cerchi per rispettare le regole del tipo di puntatore di C ed essere pronto ad avere i compilatori moderni generano codice senza senso se uno scivola, anche se compilatori più vecchi avrebbero generato codice che funziona come previsto, oppure documentano un requisito secondo cui il codice sarà utilizzabile solo con compilatori configurati per supportare il comportamento del puntatore vecchio stile (ad es. un "-fno-rigoroso-aliasing") Alcune persone considerano il "-fno-rigoroso aliasing" come un male, ma suggerirei che è più utile pensare al "-fno-rigoroso-aliasing" C come un linguaggio che offre una potenza semantica maggiore per alcuni scopi rispetto alla C "standard",ma a scapito delle ottimizzazioni che potrebbero essere importanti per altri scopi.
A titolo di esempio, sui compilatori tradizionali, i compilatori storici interpretano il seguente codice:
struct pair { int i1,i2; };
struct trio { int i1,i2,i3; };
void hey(struct pair *p, struct trio *t)
{
p->i1++;
t->i1^=1;
p->i1--;
t->i1^=1;
}
eseguendo i seguenti passaggi nell'ordine: incrementare il primo membro di *p
, integrare il bit più basso del primo membro di *t
, quindi decrementare il primo membro di *p
e completare il bit più basso del primo membro di *t
. I compilatori moderni riorganizzeranno la sequenza delle operazioni in un modo che codifichi che sarà più efficiente se p
e t
identificherà oggetti diversi, ma cambierà il comportamento se non lo fanno.
Questo esempio è ovviamente deliberatamente inventato, e in pratica il codice che utilizza un puntatore di un tipo per accedere ai membri che fanno parte della sequenza iniziale comune di un altro tipo di solito funzionerà, ma sfortunatamente poiché non c'è modo di sapere quando tale codice potrebbe fallire non è possibile utilizzarlo in modo sicuro se non disabilitando l'analisi di aliasing basata sul tipo.
Un esempio un po 'meno inventato si verificherebbe se si volesse scrivere una funzione per fare qualcosa come scambiare due puntatori a tipi arbitrari. Nella stragrande maggioranza dei compilatori "1990s C", ciò potrebbe essere realizzato tramite:
void swap_pointers(void **p1, void **p2)
{
void *temp = *p1;
*p1 = *p2;
*p2 = temp;
}
Nella norma C, tuttavia, si dovrebbe usare:
#include "string.h"
#include "stdlib.h"
void swap_pointers2(void **p1, void **p2)
{
void **temp = malloc(sizeof (void*));
memcpy(temp, p1, sizeof (void*));
memcpy(p1, p2, sizeof (void*));
memcpy(p2, temp, sizeof (void*));
free(temp);
}
Se *p2
viene conservato nella memoria allocata e il puntatore temporaneo non viene conservato nella memoria allocata, il tipo effettivo di *p2
diventerà il tipo di puntatore temporaneo e il codice che tenta di utilizzare *p2
come qualsiasi tipo che non corrisponde al puntatore temporaneo type invocherà Undefined Behaviour. È sicuramente estremamente improbabile che un compilatore noterebbe una cosa del genere, ma poiché la moderna filosofia del compilatore richiede che i programmatori evitino il comportamento indefinito a tutti i costi, non riesco a pensare a nessun altro modo sicuro di scrivere il codice sopra senza utilizzare l'archiviazione allocata .