Przekształcanie wskaźników to częsta operacja w języku C, która pozwala na korzystanie z uogólnionych interfejsów, na przykład pracy z pamięcią przez void* lub implementacji uniwersalnych struktur danych. Jednak przekształcanie typów wskaźników wiąże się z określonymi ryzykami i podlega ścisłym standardom.
void* w C przechowuje adres, ale nie zna typu zawartości, do którego wskazuje. Każdy inny wskaźnik (na przykład int*, char*, struct mytype*) może być jawnie (lub niejawnie) przekształcony do void* i z powrotem bez utraty informacji (o ile nie występuje "skurczenie").void*), musisz być pewien, że pod tym adresem rzeczywiście znajduje się wartość zgodnego typu.void process(void *data) { int *arr = (int*)data; // używamy arr jako tablicy int } int main() { double x = 10; process(&x); // NIEBEZPIECZNE: przekształcamy double* na int*, UB }
"Czy każdy wskaźnik może być bezpiecznie przekształcony do void* i z powrotem bez strat?"
Wielu odpowiada "tak" — ponieważ standard C gwarantuje przekształcenie każdego wskaźnika obiektowego do void* i z powrotem bez strat. Ale ważne jest, aby pamiętać: jeśli przekształcisz wskaźnik nie-obiektowy (na przykład wskaźnik na funkcję) do void* lub pomieszasz różne architektury (rozmiary wskaźników różnią się dla funkcji i danych), to otrzymasz nieokreślone zachowanie.
void foo() {} void *p = (void*)foo; // UB! wskaźnik na funkcję nie może być tak przekształcany
Historia
W projekcie z międzyplatformowym systemem przetwarzania danych użyto handlera, który przekształcał wskaźnik na strukturę do
void*, a następnie z powrotem do oryginalnego typu. Przy przejściu na architekturę, w którejint*idouble*miały różne wyrównania, próba przekształceniavoid*do niewłaściwego typu doprowadziła do "bus error" (awarii).
Historia
W projekcie embedded programista zaimplementował bufor cykliczny z uniwersalnym interfejsem na
void*, ale zapomniał o surowych wymaganiach dotyczących wyrównania (przydzielał pamięć pod tablicęchar, przekazując jakoint*). Na niektórych platformach dane stały się "niezrozumiałe dla sprzętu" — wystąpiły błędy odczytu i niestabilna praca.
Historia
W dynamicznej kolekcji używano przechowywania adresów jako
void*, ale zapomniano, że wskaźnik na funkcję nie może być przekształcany dovoid*. Próba przechowywania i przekazywania handlera zdarzeń (callback) przez takie pole doprowadziła do awarii tylko na części platform, a wychwycenie błędu było ekstremalnie trudne.