Książka C ++, którą czytałem, stwierdza, że kiedy wskaźnik jest usuwany za pomocą delete
operatora, pamięć w miejscu, na które wskazuje, jest „zwalniana” i można ją nadpisać. Stwierdza również, że wskaźnik będzie nadal wskazywał tę samą lokalizację, dopóki nie zostanie ponownie przypisany lub ustawiony na NULL
.
Jednak w programie Visual Studio 2012; wydaje się, że tak nie jest!
Przykład:
#include <iostream>
using namespace std;
int main()
{
int* ptr = new int;
cout << "ptr = " << ptr << endl;
delete ptr;
cout << "ptr = " << ptr << endl;
system("pause");
return 0;
}
Kiedy kompiluję i uruchamiam ten program, otrzymuję następujące dane wyjściowe:
ptr = 0050BC10
ptr = 00008123
Press any key to continue....
Oczywiście adres wskazywany przez wskaźnik zmienia się po wywołaniu funkcji usuwania!
Dlaczego to się dzieje? Czy ma to coś wspólnego z programem Visual Studio?
A jeśli delete może zmienić adres, na który i tak wskazuje, dlaczego nie miałby automatycznie ustawiać wskaźnika na NULL
zamiast jakiegoś losowego adresu?
źródło
Odpowiedzi:
Zauważyłem, że zapisany adres
ptr
był zawsze nadpisywany przez00008123
...Wydawało się to dziwne, więc poszperałem trochę i znalazłem ten wpis na blogu firmy Microsoft zawierający sekcję omawiającą „Automatyczne oczyszczanie wskaźnika podczas usuwania obiektów C ++”.
Nie tylko wyjaśnia, co program Visual Studio robi ze wskaźnikiem po jego usunięciu, ale także wyjaśnia, dlaczego NIE ustawili go na
NULL
automatycznie!Ta „funkcja” jest włączona w ramach ustawienia „Kontrole SDL”. Aby go włączyć / wyłączyć, przejdź do: PROJECT -> Właściwości -> Właściwości konfiguracji -> C / C ++ -> Ogólne -> Testy SDL
Aby to potwierdzić:
Zmiana tego ustawienia i ponowne uruchomienie tego samego kodu daje następujący wynik:
ptr = 007CBC10 ptr = 007CBC10
„funkcja” jest w cudzysłowie, ponieważ w przypadku, gdy masz dwa wskaźniki do tej samej lokalizacji, wywołanie funkcji usuwania wyczyści tylko JEDNO z nich. Drugi będzie wskazywał nieprawidłową lokalizację ...
AKTUALIZACJA:
Po kolejnych 5 latach programowania w C ++ zdałem sobie sprawę, że cała ta kwestia jest w zasadzie kwestią sporną. Jeśli jesteś programistą C ++ i nadal używasz surowych wskaźników
new
idelete
zarządzasz nimi zamiast inteligentnych wskaźników (które omijają cały ten problem), możesz rozważyć zmianę ścieżki kariery, aby zostać programistą C. ;)źródło
0x8123
zamiast0
. Wskaźnik jest nadal nieprawidłowy, ale powoduje wyjątek podczas próby wyłuskiwania go (dobrze) i nie przechodzi kontroli NULL (również dobrze, ponieważ nie można tego zrobić). Gdzie jest miejsce na złe nawyki? To naprawdę jest coś, co pomaga w debugowaniu.Zobaczysz efekty uboczne
/sdl
opcji kompilacji. Domyślnie włączona dla projektów VS2015, umożliwia dodatkowe kontrole bezpieczeństwa poza tymi zapewnianymi przez / gs. Użyj ustawienia Projekt> Właściwości> C / C ++> Ogólne> SDL sprawdza ustawienie, aby je zmienić.Cytując z artykułu MSDN :
Należy pamiętać, że ustawienie usuniętych wskaźników na NULL jest złą praktyką podczas korzystania z MSVC. To pokonuje pomoc, którą otrzymujesz zarówno ze sterty debugowania, jak i tej opcji / sdl, nie możesz już wykrywać nieprawidłowych wywołań free / delete w swoim programie.
źródło
delete
, program Visual Studio pozostawi drugi wskaźnik wskazujący jego oryginalną lokalizację, która jest teraz nieprawidłowa.To jest zdecydowanie myląca informacja.
Jest to wyraźnie zgodne ze specyfikacjami językowymi.
ptr
nie jest ważny po wywołaniudelete
. Używanieptr
podelete
d jest przyczyną niezdefiniowanego zachowania. Nie rób tego. Środowiskoptr
wykonawcze może wykonywać dowolne czynności po wywołaniudelete
.Zmiana wartości wskaźnika na jakąkolwiek starą wartość mieści się w specyfikacji języka. Jeśli chodzi o zmianę na NULL, powiedziałbym, że byłoby źle. Program zachowywałby się bardziej rozsądnie, gdyby wartość wskaźnika była ustawiona na NULL. Jednak to ukryje problem. Gdy program zostanie skompilowany z różnymi ustawieniami optymalizacji lub przeniesiony do innego środowiska, problem prawdopodobnie pojawi się w najbardziej nieodpowiednim momencie.
źródło
delete
dwukrotne wywołanie wskaźnika nie spowodowałoby problemu. To zdecydowanie nie jest dobre.NULL
znajdującą się poza przestrzenią adresową procesu, ale także zdecydowanie poza przestrzenią adresową procesu, ujawni więcej przypadków niż dwie alternatywy. Pozostawienie go wiszącego niekoniecznie spowoduje segfault, jeśli zostanie użyty po uwolnieniu; ustawienie go naNULL
nie spowoduje segfault, jeśli zostaniedelete
ponownie d.delete ptr; cout << "ptr = " << ptr << endl;
Ogólnie rzecz biorąc, nawet czytanie (tak jak powyżej, uwaga: różni się to od wyłuskiwania) wartości nieprawidłowych wskaźników (wskaźnik staje się nieprawidłowy, na przykład, gdy
delete
go wykonujesz) jest zachowaniem zdefiniowanym w implementacji. Zostało to wprowadzone w CWG # 1438 . Zobacz także tutaj .Zwróć uwagę, że przed tym odczytem wartości nieprawidłowych wskaźników było niezdefiniowane zachowanie, więc to, co masz powyżej, byłoby niezdefiniowanym zachowaniem, co oznacza, że wszystko może się zdarzyć.
źródło
[basic.stc.dynamic.deallocation]
: „Jeśli argument podany funkcji cofnięcia alokacji w bibliotece standardowej jest wskaźnikiem, który nie jest wartością wskaźnika zerowego, funkcja cofnięcia alokacji powinna zwolnić miejsce przechowywania, do którego odwołuje się wskaźnik, unieważniając wszystkie wskaźniki odnoszące się do dowolnego część zwolnionej pamięci ”i reguła w[conv.lval]
(sekcja 4.1), która mówi, że odczytywanie (konwersja lwartości-> rwartości) jakiejkolwiek nieprawidłowej wartości wskaźnika jest zachowaniem zdefiniowanym przez implementację.Wydaje mi się, że używasz pewnego rodzaju trybu debugowania, a VS próbuje ponownie wskazać wskaźnik do jakiejś znanej lokalizacji, aby dalsza próba wyłuskiwania mogła zostać wyśledzona i zgłoszona. Spróbuj skompilować / uruchomić ten sam program w trybie wydania.
Wskaźniki zwykle nie są zmieniane wewnątrz
delete
ze względu na wydajność i uniknięcie fałszywego wyobrażenia o bezpieczeństwie. Ustawienie wskaźnika usuwania na wstępnie zdefiniowaną wartość nie przyniesie żadnych korzyści w większości złożonych scenariuszy, ponieważ usuwany wskaźnik będzie prawdopodobnie tylko jednym z kilku wskazujących na tę lokalizację.Prawdę mówiąc, im więcej o tym myślę, tym bardziej uważam, że VS jest winny, gdy to robię, jak zwykle. A co, jeśli wskaźnik jest const? Czy nadal to zmieni?
źródło
Po usunięciu wskaźnika pamięć, na którą wskazuje, może nadal działać. Aby zamanifestować ten błąd, wartość wskaźnika jest ustawiana na oczywistą wartość. To naprawdę pomaga w procesie debugowania. Jeśli wartość została ustawiona na
NULL
, może nigdy nie pojawić się jako potencjalny błąd w przebiegu programu. Więc może ukryć błąd podczas późniejszego testowaniaNULL
.Inną kwestią jest to, że jakiś optymalizator czasu wykonywania może sprawdzić tę wartość i zmienić jej wyniki.
We wcześniejszych czasach MS ustawiało tę wartość na
0xcfffffff
.źródło