Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?

Co z tym NULLem? (i książką Symfonia)

Ostatnio zmodyfikowano 2009-09-02 17:05
Autor Wiadomość
pompom
» 2009-09-01 21:13:59
Technicznie rzecz biorąc, nie jest (słowo kluczowe = słowo zarezerwowane).
Można go uznać za słowo kluczowe nie wspierane przez kompilator.
P-9999
aRusher
Temat założony przez niniejszego użytkownika
» 2009-09-01 23:26:01
Cóż, to trochę... dziecinne. Najpierw piszesz:
Dobra, zrobię wywód wyjaśniający NULL (sens jej istnienia, no prawie jak sens życia po prostu chytry).
A potem, gdy o 'Twoim' tekście ktoś się niepochlebnie wyraził, dajesz nam do zrozumienia, że go przekopiowałeś skądś i tylko zmodyfikowałeś.

I nie, nie przeoczyłem fragmentu:
Ale to przecież tylko post - ja takie rzeczy robię, jak ktoś mnie zdenerwuje i mam chwilę czasu oczko. Jakby mnie ten ktoś nie zdenerwował, to może sam bym coś napisał.
Choć czytając to poprzednio, nie bardzo zrozumiałem o co chodzi w tym rzuconym mimochodem "sam bym coś napisał", przyjmując Twoje autorstwo jako pewnik.
P-10000
aRusher
Temat założony przez niniejszego użytkownika
» 2009-09-02 19:05:50
Tak, tak, niech Ci będzie ;)
P-10011
manfred
» 2009-09-01 07:09:57
będące ich konsekwencją zera mają inne działanie
Interpretacja kontekstowa literałów - dowolne bezstanowe wyrażenie o wartości 0 (nawet 74*69-5106) może być zinterpretowane jako zero wskaźnikowe, odpowiedniego typu (czyli nie void*, tylko w przypadku "int* hedgehog = 0" będzie to int*).

Ta 'stała' bo tak to w sumie można potocznie nazwać
Nie można, czego byś nie zrobił to makro nie będzie nawet stałej przypominało.
Oczywiście są takie stworki, które mają wylane na ogólno przyjęte zasady stylu programowania i tworzą 'nowe' własne zasady
Kto to mówi :P? Hm... Jeśli dobrze rozumiem, to stwierdziłeś, że Bjarne Stroustrup ma "wylane na ogólno przyjęte zasady stylu programowania i tworzy 'nowe' własne zasady", pogratulować...

Dobra, zrobię wywód wyjaśniający NULL (sens jej istnienia, no prawie jak sens życia po prostu :>).
Skąd się zatem wzięło NULL i dlaczego nie ma go w C++ (tzn. jest, ale tylko jako część C, której nie wycofano z uwagi na to, że nie ma bardzo jak zrobić sobie tym kuku)?
Nie ma co ukrywać, C dużo czerpie z Pascala. Jak w każdym języku, który posiada wskaźniki, musiano zrobić taką wartość wskaźnika, która wskazuje w próżnię - zwie się to "wskaźnikiem pustym". W Pascalu mamy über-pr0 słowo kluczowe nil, które pełni tą funkcję. Jednakże ponieważ C miał służyć do "prawdziwego" programowania, a nie do zabawy, null pointer oznaczono po prostu zerem i na tym koniec.
NULL wprowadzono do C z tylko jednego powodu - w wielu implementacjach int był mniejszy niż rozmiar typu wskaźnikowego, przez co należało pisać 0L zamiast 0 (w tamtych czasach nie było konwersji typów ani interpretacji kontekstowej literałów). Twierdzenie, że NULL ma cokolwiek wspólnego z "jawnym deklarowaniem, o co tak naprawdę chodzi" (często się tak rzutowania uzasadnia) jest wierutną bzdurą, głównie dlatego, że to "deklarowanie o co chodzi" jest tak łysy grzywką o kant kuli - nigdy się nie zdarzyło, żeby jawne rzutowanie przyczyniało się do poprawy jakości softu, zaś potrafi się stać źródłem błędów (bez rzutowania kompilator mógłby wywalić z powodu jakiejś nieścisłości warning, a tak się go chamsko oszukuje). NULL jest więc czystym ideologizmem, zwłaszcza od czasu, gdy C ma już automatyczne konwersje i 0 może być potraktowane jako pusty wskaźnik, nawet jeśli jego reprezentacja nie jest ciągiem zerowych bitów.
Po wprowadzeniu do C statycznej kontroli typów (a raczej jej drobnej namiastki) stwierdzono, że typ void* będzie typem uniwersalno-wskaźnikowym (jak pointer z Pascala; wcześniej do tego służył char*), który może się niejawnie konwertować z innym wskaźnikiem (w C konwersje między różnymi wskaźnikami są dopuszczone przez standard, ale zwykle kompilator wywala wtedy warning, jednakże nie w sytuacji, gdy w konwersji bierze udział void*). Wszyscy ideologiczni wykładowcy języka C zaczęli restrykcyjnie wymagać tej "specjalnej stałej NULL", wskutek czego w programach w C jest ona bardzo często używana. Niewielu się na ten idiotyzm nie dało nabrać. Zwłaszcza, że ANSI C nie tylko nie zabrania używać 0 zamiast NULL (0 nie musi być rzutowane na void*, jak to ma miejsce w definicji NULL w wielu kompilatorach), ale też pozwala niejawnie konwertować każdą wartość wskaźnika na int (w C++ najwyżej można na bool, a w C konwersja między dowolnym typem całkowitym, nawet char, a wskaźnikowym odbywa się niejawnie bez problemów, może tylko co bardziej czepialski kompilator rzuca warning). Jednak zdefiniowanie void* jako typu uniwersalno-wskaźnikowego pozwoliło zrobić z NULL (void*)0 i przypisywać rezultat malloc bez rzutowania. Paradoksalnie, ta głupia właściwość spowodowała, że NULL w C lepiej spełnia swoje zadanie niż w C++.
Dlaczego głupia? Otóż stanowi poważną dziurę (niemalże ozonową :>) w systemie typów C. Niedopuszczalne jest, aby bez rzutowania przypisać wartość typu void* do typu Hedgehog*, czyli w sposób niejawny złamać system typów (bo to spowoduje zmianę interpretacji kawałka pamięci). Nie mówię, że tej operacji nie powinno się wykonywać, ale w C++ można tak tylko statycznym rzutowaniem. Przyglądając się jakimkolwiek źródłom w C, można zobaczyć, że nikt nie dokonuje konwersji między wskaźnikami bez rzutowania. Zabraniają tego takoż wszelkie standardy kodowania. Oczywiście też czepiają się tego kompilatory, tylko że nie w sytuacji konwersji z udziałem void* (malloc!).
Twórcy C++ nie mogli sobie pozwolić na taki wyrwy, uważając je za uzasadnioną niezgodność. Nie ma więc nic takiego, jak typ uniwersalno-wskaźnikowy, ani specjalna stała NULL (bo nie ma w niej nic specjalnego). Przyjęto po prostu, że literał 0 (a także dowolne bezstanowe wyrażenie o wartości 0) może być zinterpretowane jako wartość typu wskaźnikowego, tworząc wartość zwaną "pustym wskaźnikiem", ale uwaga, typu tego wskaźnika. Z uwagi na statyczny system typów C++ tylko tak można to pojęcie prawidłowo określić. Dlatego w C++ NULL definiuje się po prostu jako 0, gdyż jego wycofanie byłoby nieuzasadnioną niezgodnością z C.
Najlepszy dowcip polega na tym, że NULL jako "wartość odpowiedniego typu" bardziej pasuje do języków takich jak Smalltalk. W C NULL się przyjęło, ale każdy doświadczony programista wie, że jest ono czystą hipokryzją. Smalltalk w ogóle nie posiada typizacji, zatem każde odniesienie do obiektu to wartość tego samego typu. Dlatego w Smalltalku ustanowiono taki obiekt, którego nie ma (coś jak urządzenie NUL z Windowsa czy /dev/null na uniksopodobnych). Ten obiekt tak naprawdę jest, ale jego odniesienie jest taką "wartością szczególną" wśród wartości odniesień i to wszystko (mamy taką wartość zawsze dostepną, możemy sobie jakieś odniesienie z nim porównywać, możemy ją zwracać jako kod błędu, podawać jako nie-obiekt i takie tam). Zasadniczo więc NULL z C i nil ze Smalltalka różnią się tym, że Smalltalkowe nil odnosi się do faktycznie istniejącego obiektu, a NULL nie.
Cały bajer w tym, iż mało kto zdaje sobie sprawę z konsekwencji tego, że NULL jest niewyłuskiwalne. Równie dobrze jak 0 mogłoby to być 74. To, że oznacza się to jako 0 i zawsze jest niewyłuskiwalne jest tylko kwestią małej "umowy" między twórcami bibliotek a użytkownikiem (tak, kwestia bibliotek, nie języka!), istnieje tylko językowe wspomaganie. Jaki to problem, żeby - również w C - np. dla typu "struct Hedgehog" utworzyć globala typu "struct Hedgehog*" o nazwie "null_hedgehog", który wskazywałby na jakiś istniejący obiekt, ale w przypadku odwołania notował to zdarzenie jako błąd? Twórca biblioteki nie musi używać NULL. NULL jest o tyle dobre, że jest już zdefiniowane. I jest to po prostu tylko wartość, która nigdy nie zostanie zwrócona jako prawidłowy adres i może być zwracana jako kod błędu oraz podawana jako nie-obiekt. Niemniej używanie NULL się nie różni niczym od używania zera (no dobra, trzy znaki więcej trzeba w przypadku NULL wpisać :>). Zupełnie sprzecza się to z koncepcją nulla (nila) z innych języków obiektowych (akurat Java i C# tylko zmałpowały zachowanie NULL z C++). Różnica między NULL a nil jest taka, jakby nil było plikiem /dev/null, a NULL linkiem symbolicznym do jakiegoś pliku, który na pewno nie istnieje. W Smalltalku czy Objective-C można robić co się chce z nullem, a null się nie obrazi. W C (i C++) natomiast na sto procent wiemy, że ta wartość nigdy nie wskazuje obiektu i na sto procent próba odwołania się do niego (pod systamch z ochroną pamięci) zakończy się pójściem w buraki.
Ponieważ C++ jest językiem z (w miarę...) silną typizacją, ze wszystkimi tego konsekwencjami, toteż w nim musimy wskaźnik/referencję stworzyć do konkretnego typu, a za pośrednictwem takiego stwora można wywołać na rzecz obiektu tylko te metody, które dla tego typu zdefiniowano. Właściwość ta sprawiła, że NULLa jako wartość konkretnego typu zrobić się nie da. Jedyne sensowne NULL dla typu np. Hedgehog to taka stała:
const Hedgehog * const null_hedgehog = 0;
Znam jednakże lepszą definicję null, która spełnia to, co NULL w C. Niemniej nic nie stoi na przeszkodzie, żeby używać 0, zwłaszcza, że NULL w C++ ma właśnie taką definicję (zatem używając 0 unikasz hipokryzji i oszukiwania się). Przynajmniej według standardu, bo niektóre kompilatory definiują ją jako specjalne słowo kluczowe (w g++ jest to __null). Nie zmienia to jednak faktu, że programy zachowują się tak samo, a rozstrzyganie przeciążenia przy podanym NULL jest zawsze na korzyść int, zyskuje się tylko dodatkowe ostrzeżenie. Przedstawiona niżej wersja null rozwiązuje to oczywiście na korzyść typu wskaźnikowego:
C/C++
static const struct empty_pointer_value
{
    template < typename T >
    operator T *() const
    {
        return 0;
    }
    template < typename C, typename T >
    operator T C::*() const
    {
        return 0;
    }
} null;
Nazwałem to null, żeby nazwa nie kolidowała z NULL, bowiem wartość ta jest objęta innymi regułami, niż NULL, którego reguły są identyczne z bezstanowym 0. Tak zdefiniowane null adoptuje się do każdego wskaźnika, można je przypisywać, przekazywać jako argument, a także porównywać.
Amen.
P-19257
manfred
» 2009-09-01 14:50:07
Na artykuł by się nadawało jak nic.
Ale to przecież tylko post - ja takie rzeczy robię, jak ktoś mnie zdenerwuje i mam chwilę czasu ;). Jakby mnie ten ktoś nie zdenerwował, to może sam bym coś napisał.
Taka niespodzianka, nie spodziewałbym się takiego zaangażowania z Twojej strony
Widocznie mnie nie znasz.
chapeau!
... bas? :P
P-19258
manfred
» 2009-09-01 15:19:46

1. Jawnie rzutować, jeśli trzeba. W zasadzie dla pozbycia się warningów konwersję double->int też powinno się rzutowaniem robić.
2. Nie, void* to nie pozostałość. Oczywiście, jeśli można to void* należy unikać, używając w zamian wskaźnika do typu, o który naprawdę nam chodzi.
Wyrażenie bezstanowe to (w tym kontekście przynajmniej) takie, które używa tylko literałów (nie wiem jak ze stałymi), więc można je obliczyć w czasie kompilacji.
P-19259
manfred
» 2009-09-01 15:23:09
A z przeciążaniem/przeładowaniem jest taki cyrk, że overloading znaczy dosłownie "przeciążanie". Przeładować to można towar z jednej ciężarówki do drugiej - po angielsku reload, co z overload wiele wspólnego nie ma.
//nie będę robił wykładniczego przyrostu zajętego miejsca w bazie robił edycją postów ;P
P-19260
manfred
» 2009-09-01 19:51:46
Jest? Gdzie? To jest tylko konsekwencja faktu, że wszystkie typy są pochodną void, a jak wiadomo, konwersja ze wskaźnika na typ pochodny na wskaźnik na typ bazowy wykonuje się najzupełniej legalnie i poprawnie. Czyli jeśli po klasie A dziedziczy 1000000 innych klas, to A* nie jest przecież jakimś uniwersalnym wskaźnikiem, tylko wskaźnikiem na A i nic więcej (obiekt klasy pochodnej JEST obiektem klasy bazowej).
Tak, literał == stała dosłowna.
P-19261
1 « 2 » 3
Poprzednia strona Strona 2 z 3 Następna strona