2010-04-14 9 views
12

To jest kod C, który zwalnia pamięć pojedynczo połączonej listy. Jest skompilowany z Visual C++ 2008 i kod działa tak, jak powinien.Pytanie o kompilatory i jak działają

/* Program done, so free allocated memory */ 
current = head; 
struct film * temp; 
temp = current; 
while (current != NULL) 
{ 
    temp = current->next; 
    free(current); 
    current = temp; 
} 

Ale ja też spotkałem (nawet w książkach) sam kod napisany tak:

/* Program done, so free allocated memory */ 
current = head; 
while (current != NULL) 
{ 
    free(current); 
    current = current->next; 
} 

Jeśli mogę skompilować ten kod z moich VC++ 2008, awarie programu, bo jestem pierwszy uwalniając prąd, a następnie przypisywanie prądu-> obok aktualnego. Ale oczywiście jeśli skompiluję ten kod z innym kompilatorem (na przykład kompilatorem, którego używał autor książki) program zadziała. Pytanie brzmi, dlaczego ten kod został skompilowany z konkretną pracą kompilatora? Czy to dlatego, że ten kompilator umieszczał instrukcje w pliku binarnym, które pamiętają adres bieżącego-> następnego, chociaż uwolniłem prąd i mój VC++ tego nie robi. Chcę tylko zrozumieć, jak działają kompilatory.

+2

które książki to będzie? –

+4

@Neil, złe. –

+10

Prosimy o przekazanie nam książki, abyśmy mogli jej uniknąć i zalecić jej odrzucenie. –

Odpowiedz

18

Drugi program wywołuje niezdefiniowane zachowanie. Nie jest to różnica w kompilatorze, ale raczej różnica w implementacji biblioteki standardowej C i funkcji free(). Kompilator będzie przechowywać wskaźnik current jako zmienną lokalną, ale nie będzie przechowywać kopii pamięci, do której się odwołuje.

Po wywołaniu wolnej(), rezygnujesz z własności pamięci wskazywanej przez wskaźnik przekazany do funkcji free(). Jest możliwe, że po zrzeczeniu się własności zawartość pamięci wskazywanej jest nadal uzasadniona i wciąż są prawidłowe lokalizacje pamięci w przestrzeni adresowej twojego procesu. W związku z tym jest możliwe, że dostęp do nich będzie działać (pamiętaj, że możesz w ten sposób uszkodzić pamięć w ten sposób). Wskaźnik, który ma wartość inną niż null i wskazuje na pamięć, która została już odrzucona, jest znany jako dangling pointer i jest niesamowicie niebezpieczny. Tylko dlatego, że może się wydawać, że działa, nie oznacza, że ​​jest poprawna.

Chciałbym również zwrócić uwagę, że możliwe jest zaimplementowanie metody free() w taki sposób, aby wychwycić te błędy, na przykład za pomocą oddzielnej strony dla każdej alokacji, i usunięcie strony po wywołaniu metody free() adres pamięci nie jest już prawidłowym adresem dla tego procesu). Takie implementacje są wysoce nieefektywne, ale czasami są używane przez niektóre kompilatory w trybie debugowania, aby wychwycić zwisające błędy wskaźnika.

+1

Prawdopodobnie łatwiej byłoby podać adres ostatnich 4 zwolnień w rejestrach DR0-DR3 i umieścić punkt przerwania odczytu we wszystkich z nich. – MSalters

3

Drugi przykład to zły kod - nie powinien odnosić się do current po zwolnieniu. Wygląda na to, że działa w wielu przypadkach, ale jest to niezdefiniowane zachowanie. Używanie takich narzędzi, jak valgrind spowoduje usunięcie takich błędów.

Proszę zacytować książki, na których widzieliście ten przykład, ponieważ musi on zostać poprawiony.

+0

http://bytes.com/topic/c/answers/212665-freeing-simple-linked-list C primer plus (wszystkie wydania, 5, 4 ...) – dontoo

+0

Dzięki - Wiedziałem, że książki, które Indianin kolegia używają do programowania C są dość złe (Kanetkar, Balaguruswami, itp.), ale myślę, że problem jest bardziej rozpowszechniony. –

11

Po wykonaniu free(current), pamięć wskazana przez current (gdzie przechowywane jest current->next) została zwrócona do biblioteki C, więc nie powinieneś już mieć do niej dostępu.

Biblioteka C może w dowolnej chwili zmienić zawartość tej pamięci - co spowoduje, że current->next zostanie uszkodzona - ale może również nie zmienić niektórych lub wszystkich jej elementów, szczególnie w tak krótkim czasie. Dlatego działa w niektórych środowiskach, a nie w innych.

To trochę jak jazda przez czerwone światło. Czasem ci się to uda, ale czasami zostaniesz przejechany przez ciężarówkę.

4

Najlepszym sposobem sprawdzenia, w jaki sposób działa kompilator, nie jest pytanie o sposób postępowania z nieprawidłowym kodem. Podczas kompilacji musisz przeczytać książkę (więcej niż jedną).Dobrym miejscem na rozpoczęcie byłoby sprawdzenie zasobów pod numerem Learning to write a compiler.

1

W rzeczywistości jest to środowisko wykonawcze C, a nie kompilator. W każdym razie ten ostatni kod zawiera niezdefiniowane zachowanie - nie rób tego. To zadziałało dla kogoś o jakiejś implementacji, ale jak widzisz, to na twoich. Mogłoby to równie dobrze uszkodzić coś w milczeniu.

Możliwe wyjaśnienie, dlaczego późniejsze może zadziałać, polega na tym, że w niektórych implementacjach free() nie modyfikuje zawartości bloku i nie zwraca bloku pamięci bezpośrednio do systemu operacyjnego, więc odwołanie do wskaźnika bloku jest nadal " prawnych ", a dane w bloku pozostają nienaruszone.

0

Czy to dlatego, że instrukcje kompilator umieścić w pliku binarnego, które pamiętają adres natężenie prądu> obok choć uwolniony prąd i moja VC++ nie robi.

Nie sądzę.

Po prostu chcę zrozumieć, jak działają kompilatory.

Oto przykład z kompilator gcc (nie mam VC++)

struct film { film* next; }; 

int main() { 
    film* current = new film(); 
    delete current; 

    return 0; 
} 

;Creation 
movl $4, (%esp) ;the sizeof(film) into the stack (4 bytes) 
call _Znwj  ;this line calls the 'new operator' 
        ;the register %eax now has the pointer 
        ;to the newly created object 

movl $0, (%eax) ;initializes the only variable in film 

;Destruction 
movl %eax, (%esp) ;push the 'current' point to the stack 
call _ZdlPv  ;calls the 'delete operator' on 'current' 

Jeśli to była klasa i posiada destruktor, to powinien być nazywany zanim uwolnić przestrzeń przedmiot zajmuje w pamięci za pomocą operatora delete.

Po zniszczeniu obiektu i zwolnieniu jego miejsca w pamięci, nie można już więcej odnosić prądu -> dalej bezpiecznie.