Historia zagadnienia: Język C zawsze był elastyczny w kwestii konwersji typów, aby ułatwić pracę z niskopoziomową pamięcią i różnymi platformami. Jednak jego zwięzłość i moc mogą łatwo prowadzić do luk oraz defektów związanych z nieprawidłowym rzutowaniem typów, szczególnie podczas pracy z wskaźnikami i arytmetyką bitową.
Problem:
Rozwiązanie:
Przykład kodu:
#include <stdio.h> void print_double_as_int(double d) { int i = (int)d; printf("Wartość: %d\n", i); } void *ptr = malloc(16); int *ip = (int*)ptr; // Dostęp do surowej pamięci: dozwolone, jeśli ptr rzeczywiście wskazuje na int
Kluczowe cechy:
1. Kiedy dozwolone jest rzutowanie void na wskaźnik do struktury i czy zawsze jest to bezpieczne?*
Takie rzutowanie jest bezpieczne, jeśli adres rzeczywiście wskazuje na instancję danej struktury, w przeciwnym razie zachowanie jest nieokreślone (undefined behavior).
2. Co się stanie, jeśli rzutować wskaźnik do struktury o jednej długości na wskaźnik do struktury z mniejszą lub większą liczbą pól?
Dostęp do pól "nowej" struktury prowadzi do odczytu/zapisu poza granicami oryginalnej struktury, co może prowadzić do uszkodzenia danych.
Przykład kodu:
typedef struct {int a;} S1; typedef struct {int a; int b;} S2; S1 s; S2 *ps2 = (S2*)&s; // ps2->b — dostęp do "śmieci"
3. Czy bezpiecznie jest rzutować wskaźnik do int na wskaźnik do char, aby uzyskać dostęp do bajtów tej liczby?
To jeden z typowych sposobów pracy z pamięcią — dostęp do bajtów jest dozwolony, ale wymaga ostrożności, ponieważ mogą wystąpić problemy z wyrównaniem, a kolejność bajtów zależy od architektury (big-endian/little-endian).
Młodszy programista, aby zoptymalizować czas dostępu, przetwarzał pakiet sieciowy, rzutując wskaźnik z surowej tablicy na wskaźnik do struktury danych z polami różnego typu.
Zalety:
Wady:
Po przeróbce każdy bajt pakietu był ręcznie wydobywany za pomocą memcpy.
Zalety:
Wady: