Kiedy twój kompilator przetłumaczy twój kod C na kod wykonywalny maszyny, wyrzuci wiele informacji, w tym informacje o typie. Gdzie piszesz:
int x = 42;
wygenerowany kod kopiuje tylko pewien wzorzec bitowy w pewnym fragmencie pamięci (fragmentu, który może być typowo 4 bajty). Nie można stwierdzić poprzez sprawdzenie kodu maszynowego, że fragment pamięci jest obiektem typu int
.
Podobnie, gdy piszesz:
if (mynode->next_node == NULL) { /* ... */ }
wygenerowany kod pobiera wskaźnik wielkości kawałek pamięci przez dereferencji innego wskaźnika wielkości kawałek pamięci, i porównać wynik z reprezentacją systemu z zerowym wskaźnikiem (zwykle wszystkie-bity-zero). Wygenerowany kod nie odzwierciedla bezpośrednio faktu, że next_node
jest członkiem struktury, ani nic o tym, jak struktura została przydzielona lub czy nadal istnieje.
Kompilator może sprawdzić wiele rzeczy w czasie kompilacji, ale niekoniecznie generuje kod do przeprowadzania kontroli w czasie wykonywania. Od Ciebie zależy, czy jako programista unikniesz błędów.
W tym konkretnym przypadku, po wywołaniu free
, mynode
ma nieokreśloną wartość. Nie wskazuje żadnego poprawnego obiektu, ale nie ma wymogu implementacji, aby cokolwiek zrobić z tą wiedzą. Wywołanie free
nie niszczy przydzielonej pamięci, a jedynie udostępnia ją do alokacji przez przyszłe połączenia z numerem malloc
.
Istnieje wiele sposobów, że implementacja mógłby przeprowadzają kontrole w ten sposób, i wywołać błąd run-time jeśli nieprawidłowego wskaźnika po free
ing go. Ale takie kontrole nie są wymagane przez język C, i generalnie nie są implementowane, ponieważ (a) byłyby one dość drogie, dzięki czemu twój program działałby wolniej i (b) kontrole i tak nie są w stanie wychwycić wszystkich błędów.
C jest tak zdefiniowany, że przydział pamięci i manipulacja kursorem będą działały poprawnie, jeśli twój program robi wszystko dobrze. Jeśli popełnisz pewne błędy, które można wykryć podczas kompilacji, kompilator może je zdiagnozować. Na przykład przypisanie wartości wskaźnika do obiektu typu liczba całkowita wymaga co najmniej ostrzeżenia o czasie kompilacji.Ale inne błędy, takie jak dereferencja ze wskaźnikiem free
powodują, że twój program ma niezdefiniowane zachowanie . To ty, jako programista, musisz przede wszystkim unikać wprowadzania tych błędów. Jeśli ci się nie uda, jesteś sam.
Oczywiście istnieją narzędzia, które mogą pomóc. Valgrind to jeden; sprytne kompilatory optymalizujące to kolejne. (Włączenie optymalizacji powoduje, że kompilator wykonuje więcej analiz kodu, co często pozwala mu diagnozować więcej błędów.) Ale ostatecznie C nie jest językiem, który trzyma rękę. Jest to ostre narzędzie - i może być używane do budowania bezpieczniejszych narzędzi, takich jak języki interpretowane, które wykonują więcej sprawdzania w czasie pracy.
Czy spodziewałbyś się, że pamięć wyparuje podczas dzwonienia za darmo? Czy nadal tam jest (lub nie) lub może mieć inne znaczenie. – wildplasser
@wildplasser jesteś tam snurkowaty? Próbuję zrozumieć nie tylko składnię języka, ale także dlaczego działa. Oczywiście pamięć nie wyparowuje. Ale kiedy piszę * wskaźnik -> członek, nie jestem pewien, jak działa to mechanika i dlaczego nadal działa, gdy wskaźnik jest wolny() d. Mam nadzieję, że ma to sens. – Ducain
Wskaźnik ma nadal tę samą wartość. Po wywołaniu free() porzuciłeś pamięć: powiedziałeś malloc/free, że nie chcesz już jej używać. Porównaj to z numerem telefonu: po tym, jak opuściłem mój załącznik telefoniczny, mój numer nie jest już ważny. Ale nadal możesz spróbować go wybrać. To może nawet ja odpowiadam przez telefon. Lub Hałasu. Albo ktoś zupełnie inny. Numer (= adres) nadal istnieje, ale jego użycie jest już nieważne. Może wskazywać na sterowanie elektrownią jądrową ... – wildplasser