Ci possono essere molti vantaggi a tale codice, ma sfortunatamente lo standard C non è stato scritto per facilitarla. I compilatori hanno storicamente offerto garanzie comportamentali efficaci al di là di quanto richiesto dallo standard che rendesse possibile scrivere tale codice in modo molto più pulito di quanto sia possibile in Standard C, ma i compilatori hanno recentemente iniziato a revocare tali garanzie in nome dell'ottimizzazione.
In particolare, molti compilatori C hanno storicamente garantito (in base alla progettazione se non nella documentazione) che se due tipi di struttura contengono la stessa sequenza iniziale, un puntatore a un tipo può essere utilizzato 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 fa uso di tale comportamento può essere molto più pulito e più sicuro rispetto al codice che non lo è, ma sfortunatamente anche se lo standard richiede che le strutture che condividono una sequenza iniziale comune debbano essere disposte allo stesso modo, vieta al codice di utilizzare effettivamente un puntatore di un tipo per accedere alla sequenza iniziale di un altro.
Di conseguenza, se vuoi scrivere un codice orientato agli oggetti in C, devi decidere (e dovresti prendere questa decisione presto) o saltare attraverso molti cerchi per rispettare le regole del tipo di puntatore di C ed essere pronto a fare in modo che i compilatori moderni generino codice non sensato se uno scivola in alto, anche se i compilatori più vecchi avrebbero generato codice che funziona come previsto, oppure documentano un requisito che il codice sarà utilizzabile solo con compilatori configurati per supportare il comportamento puntatore vecchio stile (ad esempio usando un "-fno-strict-aliasing") Alcune persone considerano "-fno-strict-aliasing" come malvagio, ma suggerirei che è più utile pensare a "-fno-strict-aliasing" C come un linguaggio che offre maggiore potenza semantica per alcuni scopi rispetto a C "standard", ma a spese di ottimizzazioni che potrebbero essere importanti per altri scopi.
Con l'esempio, sui compilatori tradizionali, i compilatori storici interpreteranno 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;
}
come eseguire i seguenti passi nell'ordine: incrementa il primo membro di *p
, completa il bit più basso del primo membro di *t
, quindi decrementa il primo membro di *p
e completa il bit più basso del primo membro di *t
. I compilatori moderni riorganizzeranno la sequenza di operazioni in modo tale che il codice sarà più efficiente se p
e t
identificano oggetti diversi, ma cambieranno il comportamento in caso contrario.
Questo esempio è ovviamente deliberatamente inventato, e in pratica il codice che usa un puntatore di un tipo per accedere ai membri che fanno parte della sequenza iniziale comune di un altro tipo di solito funziona, ma sfortunatamente dal non c'è modo di sapere quando un tale codice può fallire, non è possibile utilizzarlo in sicurezza, se non disabilitando l'analisi di aliasing basata sui tipi.
Un esempio un po 'meno forzato si verificherebbe se si volesse scrivere una funzione per fare qualcosa come scambiare due puntatori a tipi arbitrari. Nella stragrande maggioranza dei compilatori "C degli anni '90", ciò potrebbe essere realizzato tramite:
void swap_pointers(void **p1, void **p2)
{
void *temp = *p1;
*p1 = *p2;
*p2 = temp;
}
In Standard 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 mantenuto nella memoria allocata, il tipo effettivo di *p2
diventerà il tipo del puntatore temporaneo e il codice che tenta di utilizzare *p2
poiché qualsiasi tipo che non corrisponde al tipo di puntatore temporaneo invocherà il comportamento non definito. È molto improbabile che un compilatore possa notare una cosa del genere, ma dal momento che la moderna filosofia del compilatore richiede che i programmatori evitino il comportamento indefinito a tutti i costi, non posso pensare a nessun altro mezzo sicuro per scrivere il codice sopra senza usare memoria allocata .