Czy ten fragment kodu jest prawidłowy (i zdefiniowane zachowanie)?
int &nullReference = *(int*)0;
Zarówno g ++ i brzęk ++ skompilować bez ostrzeżenia, nawet podczas używania -Wall
, -Wextra
, -std=c++98
, -pedantic
, -Weffc++
...
Oczywiście referencja nie jest tak naprawdę pusta, ponieważ nie można uzyskać do niej dostępu (oznaczałoby to wyłuskiwanie pustego wskaźnika), ale możemy sprawdzić, czy jest pusta, czy nie, sprawdzając jej adres:
if( & nullReference == 0 ) // null reference
c++
reference
null
language-lawyer
peoro
źródło
źródło
if (false)
, eliminując sprawdzanie, właśnie dlatego, że odwołania i tak nie mogą być zerowe. Lepiej udokumentowana wersja istniała w jądrze Linuksa, gdzie zoptymalizowano bardzo podobne sprawdzenie NULL: isc.sans.edu/diary.html?storyid=6820Odpowiedzi:
Odnośniki nie są wskazówkami.
8.3.2 / 1:
1,9 / 4:
Jak mówi Johannes w usuniętej odpowiedzi, istnieją pewne wątpliwości, czy „wyłuskiwanie wskaźnika zerowego” powinno zostać kategorycznie uznane za niezdefiniowane zachowanie. Nie jest to jednak jeden z przypadków budzących wątpliwości, ponieważ pusty wskaźnik z pewnością nie wskazuje na „prawidłowy obiekt lub funkcję”, a komitet normalizacyjny nie pragnie wprowadzać zerowych odniesień.
źródło
&*p
jakop
uniwersalne, nie wyklucza to nieokreślonego zachowania (które z natury może „wydawać się działać”); i nie zgadzam się, żetypeid
wyrażenie, które ma na celu określenie typu „wyłuskanego wskaźnika zerowego” w rzeczywistości usuwa odwołanie do wskaźnika zerowego. Widziałem ludzi, którzy poważnie się kłócili, na czym&a[size_of_array]
nie można i nie należy polegać, a poza tym pisanie jest łatwiejsze i bezpieczniejszea + size_of_array
.*p
odnosi się lwartość , kiedyp
jest pustym wskaźnikiem. C ++ obecnie nie ma pojęcia pustej wartości l, którą chciał wprowadzić problem.typeid
pracach opartych na składni, a nie na semantyce. Oznacza to, że jeśli nietypeid(0, *(ostream*)0)
możesz zrobić mają niezdefiniowanej zachowanie - niebad_typeid
jest gwarantowana do rzucania, nawet jeśli przechodzą lwartością wynikającej z pustego wskaźnika dereference semantycznie. Ale składniowo na najwyższym poziomie nie jest to wyłuskiwanie, ale wyrażenie operatora przecinka.Odpowiedź zależy od twojego punktu widzenia:
Jeśli oceniasz według standardu C ++, nie możesz uzyskać zerowego odwołania, ponieważ najpierw otrzymujesz niezdefiniowane zachowanie. Po tym pierwszym przypadku niezdefiniowanego zachowania norma zezwala na wszystko. Tak więc, jeśli piszesz
*(int*)0
, masz już niezdefiniowane zachowanie, tak jak jesteś, z punktu widzenia standardu języka, dereferencjonowanie pustego wskaźnika. Reszta programu jest nieistotna, po wykonaniu tego wyrażenia wypadasz z gry.Jednak w praktyce odwołania o wartości null można łatwo utworzyć na podstawie wskaźników o wartości null i nie zauważysz tego, dopóki faktycznie nie spróbujesz uzyskać dostępu do wartości za odwołaniem zerowym. Twój przykład może być nieco zbyt prosty, ponieważ każdy dobry optymalizujący kompilator zobaczy niezdefiniowane zachowanie i po prostu zoptymalizuje wszystko, co od niego zależy (odniesienie zerowe nawet nie zostanie utworzone, zostanie zoptymalizowane).
Jednak ta optymalizacja zależy od kompilatora, aby udowodnić niezdefiniowane zachowanie, co może nie być możliwe. Rozważ tę prostą funkcję w pliku
converter.cpp
:Kiedy kompilator widzi tę funkcję, nie wie, czy wskaźnik jest wskaźnikiem zerowym, czy nie. Więc po prostu generuje kod, który zamienia dowolny wskaźnik w odpowiednie odniesienie. (Btw: to jest noop, ponieważ wskaźniki i referencje są dokładnie tą samą bestią w asemblerze.) Teraz, jeśli masz inny plik
user.cpp
z kodemkompilator nie wie, że
toReference()
wyłuskuje przekazany wskaźnik i zakłada, że zwraca prawidłowe odwołanie, które w praktyce będzie odwołaniem zerowym. Wywołanie kończy się powodzeniem, ale gdy próbujesz użyć odwołania, program ulega awarii. Ufnie. Standard pozwala na wszystko, w tym pojawienie się różowych słoni.Możesz zapytać, dlaczego jest to istotne, w końcu niezdefiniowane zachowanie zostało już wywołane w środku
toReference()
. Odpowiedź brzmi: debugowanie: zerowe odwołania mogą się propagować i mnożyć, tak jak robią to puste wskaźniki. Jeśli nie jesteś świadomy tego, że mogą istnieć odwołania zerowe i nauczysz się ich unikać, możesz spędzić trochę czasu próbując dowiedzieć się, dlaczego funkcja członkowska wydaje się zawieszać, gdy tylko próbuje odczytać zwykły staryint
element członkowski (odpowiedź: instancja w wywołaniu członka była odwołaniem o wartości null, więcthis
jest to wskaźnik zerowy, a Twój element członkowski jest obliczany jako adres 8).A co powiesz na sprawdzenie zerowych odwołań? Podałeś linię
w twoim pytaniu. Cóż, to nie zadziała: zgodnie ze standardem, masz niezdefiniowane zachowanie, jeśli wyłuskujesz pusty wskaźnik i nie możesz utworzyć zerowego odniesienia bez wyłuskiwania pustego wskaźnika, więc zerowe odwołania istnieją tylko w sferze niezdefiniowanego zachowania. Ponieważ Twój kompilator może założyć, że nie wyzwalasz niezdefiniowanego zachowania, może założyć, że nie ma czegoś takiego jak odwołanie o wartości null (nawet jeśli z łatwością wyemituje kod, który generuje odwołania o wartości null!). W związku z tym widzi
if()
warunek, stwierdza, że nie może być prawdziwy, i po prostu odrzuca całeif()
stwierdzenie. Wraz z wprowadzeniem optymalizacji czasu łącza sprawdzenie zerowych odwołań w solidny sposób stało się po prostu niemożliwe.TL; DR:
Puste odniesienia są trochę upiorne:
Ich istnienie wydaje się niemożliwe (= standardowo),
ale istnieją (= przez wygenerowany kod maszynowy),
ale nie możesz ich zobaczyć, jeśli istnieją (= Twoje próby zostaną zoptymalizowane),
ale i tak mogą cię zabić nieświadomego (= Twój program ulega awarii w dziwnych momentach lub gorzej).
Twoją jedyną nadzieją jest to, że one nie istnieją (= napisz swój program, aby ich nie tworzyć).
Mam nadzieję, że nie będzie cię to prześladować!
źródło
Jeśli twoim zamiarem było znalezienie sposobu na reprezentowanie wartości null w wyliczaniu pojedynczych obiektów, to złym pomysłem jest (de) odwołanie do wartości null (to C ++ 11, nullptr).
Dlaczego nie zadeklarować statycznego obiektu pojedynczego, który reprezentuje wartość NULL w klasie, w następujący sposób i dodać operator rzutowania na wskaźnik, który zwraca wartość nullptr?
Edycja: Poprawiono kilka błędów i dodałem instrukcję if w main (), aby sprawdzić, czy operator rzutowania na wskaźnik faktycznie działa (o czym zapomniałem ... mój zły) - 10 marca 2015 -
źródło
clang ++ 3.5 ostrzega nawet przed tym:
źródło