Kod ten może mieć wiele zalet, ale niestety nie napisano standardu C, aby to ułatwić. Kompilatory w przeszłości oferowały skuteczne gwarancje behawioralne, wykraczające poza to, co wymagał standard, co pozwoliło na napisanie takiego kodu znacznie czystiej niż jest to możliwe w standardzie C, ale ostatnio kompilatory zaczęły odwoływać takie gwarancje w imię optymalizacji.
Co najważniejsze, wiele kompilatorów C ma historycznie gwarancję (na podstawie projektu, jeśli nie dokumentację), że jeśli dwa typy struktur zawierają tę samą sekwencję początkową, można użyć wskaźnika do dowolnego typu, aby uzyskać dostęp do elementów tej wspólnej sekwencji, nawet jeśli typy nie są ze sobą powiązane, a ponadto, że do celów ustanowienia wspólnej sekwencji początkowej wszystkie wskaźniki do struktur są równoważne. Kod, który wykorzystuje takie zachowanie, może być znacznie bardziej przejrzysty i bezpieczniejszy dla typu niż kod, który tego nie robi, ale niestety, mimo że Standard wymaga, aby struktury dzielące wspólną sekwencję początkową musiały być ułożone w ten sam sposób, zabrania kodowi faktycznego używania wskaźnik jednego typu, aby uzyskać dostęp do początkowej sekwencji innego.
W związku z tym, jeśli chcesz napisać obiektowy kod w C, musisz zdecydować (i powinieneś podjąć tę decyzję wcześniej), aby albo przeskoczyć przez wiele obręczy, aby przestrzegać reguł typu C wskaźnika i być przygotowanym na nowoczesne kompilatory generują nonsensowny kod, jeśli ktoś się poślizgnie, nawet jeśli starsze kompilatory wygenerowałyby kod, który działa zgodnie z przeznaczeniem, lub dokumentują wymaganie, aby kod był użyteczny tylko z kompilatorami skonfigurowanymi do obsługi zachowania wskaźnika w starym stylu (np. „-fno-ścisłe-aliasing”) Niektórzy ludzie uważają „-fno-ścisłe-aliasing” za złe, ale sugerowałbym, że bardziej pomocne jest myślenie o „-fno-ścisłym-aliasingu” jako języku, który oferuje większą moc semantyczną do niektórych celów niż „standardowe” C,ale kosztem optymalizacji, które mogą być ważne dla innych celów.
Na przykład w tradycyjnych kompilatorach historyczne kompilatory interpretują następujący kod:
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;
}
wykonując następujące kroki w kolejności: zwiększ pierwszy element *p, uzupełnij najniższy bit pierwszego elementu *t, a następnie zmniejsz pierwszy element *pi uzupełnij najniższy bit pierwszego elementu *t. Nowoczesne kompilatory przestawią sekwencję operacji w taki sposób, który kod będzie bardziej wydajny, jeśli pi tzidentyfikuje różne obiekty, ale zmieni zachowanie, jeśli tego nie zrobią.
Ten przykład jest oczywiście celowo opracowany i w praktyce kod, który używa wskaźnika jednego typu, aby uzyskać dostęp do elementów wchodzących w skład wspólnej sekwencji początkowej innego typu, zwykle działa, ale niestety, ponieważ nie ma możliwości dowiedzenia się, kiedy taki kod może zawieść w ogóle nie można bezpiecznie z niego korzystać, z wyjątkiem wyłączenia analizy aliasingu opartej na typach.
Nieco wymyślony przykład miałby miejsce, gdyby ktoś chciał napisać funkcję umożliwiającą zamianę dwóch wskaźników na dowolne typy. W zdecydowanej większości kompilatorów C z lat 90. można to osiągnąć poprzez:
void swap_pointers(void **p1, void **p2)
{
void *temp = *p1;
*p1 = *p2;
*p2 = temp;
}
Jednak w standardzie C należałoby użyć:
#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);
}
Jeśli *p2jest przechowywany w przydzielonej pamięci, a wskaźnik tymczasowy nie jest przechowywany w przydzielonej pamięci, efektywny typ *p2stanie się typem wskaźnika tymczasowego i kodu, który próbuje użyć *p2jako dowolnego typu, który nie pasuje do wskaźnika tymczasowego type wywoła niezdefiniowane zachowanie. Na pewno jest bardzo mało prawdopodobne, aby kompilator zauważył coś takiego, ale ponieważ współczesna filozofia kompilatora wymaga, aby programiści unikali Nieokreślonego Zachowania za wszelką cenę, nie mogę wymyślić żadnego innego bezpiecznego sposobu pisania powyższego kodu bez użycia przydzielonej pamięci .