Czy ktoś może wyjaśnić, dlaczego wskaźniki nie są inicjalizowane NULL
?
Przykład:
void test(){
char *buf;
if (!buf)
// whatever
}
Program nie wkroczyłby do if, ponieważ buf
nie jest zerowe.
Chciałbym wiedzieć, dlaczego, w jakim przypadku potrzebujemy zmiennej z włączonym koszem, a zwłaszcza wskaźników adresujących śmieci w pamięci?
c++
memory
pointers
initialization
Jonathan
źródło
źródło
Odpowiedzi:
Wszyscy zdajemy sobie sprawę, że wskaźnik (i inne typy POD) powinny zostać zainicjalizowane.
Pojawia się wtedy pytanie „kto powinien je zainicjować”.
Cóż, są zasadniczo dwie metody:
Załóżmy, że kompilator zainicjował dowolną zmienną, która nie została jawnie zainicjowana przez programistę. Następnie napotykamy sytuacje, w których inicjalizacja zmiennej była nietrywialna, a programista nie zrobił tego w punkcie deklaracji, że musiał wykonać jakąś operację, a następnie przypisać.
Mamy więc teraz sytuację, w której kompilator dodał do kodu dodatkową instrukcję, która inicjalizuje zmienną do wartości NULL, a następnie kod programisty jest dodawany w celu wykonania poprawnej inicjalizacji. Lub w innych warunkach zmienna potencjalnie nigdy nie jest używana. Wielu programistów C ++ w obu przypadkach wrzeszczałoby w obu przypadkach kosztem tej dodatkowej instrukcji.
Nie chodzi tylko o czas. Ale także przestrzeń. Istnieje wiele środowisk, w których oba zasoby są na wagę złota, a programiści też nie chcą się poddawać.
ALE : Możesz zasymulować efekt wymuszenia inicjalizacji. Większość kompilatorów ostrzeże Cię o niezainicjowanych zmiennych. Dlatego zawsze ustawiam poziom ostrzegawczy na najwyższym możliwym poziomie. Następnie powiedz kompilatorowi, aby traktował wszystkie ostrzeżenia jako błędy. W tych warunkach większość kompilatorów wygeneruje błąd dla zmiennych, które nie są zainicjowane, ale są używane, co uniemożliwi wygenerowanie kodu.
źródło
Cytując Bjarne Stroustrup w TC ++ PL (wydanie specjalne str. 22):
źródło
D
robi. Jeśli nie chcesz inicjalizacji, użyj tej składnifloat f = void;
lubint* ptr = void;
. Teraz jest inicjalizowany domyślnie, ale jeśli naprawdę potrzebujesz, możesz powstrzymać kompilator.Ponieważ inicjalizacja wymaga czasu. W C ++ pierwszą rzeczą, którą powinieneś zrobić z dowolną zmienną, jest jawne zainicjowanie jej:
lub:
lub:
źródło
Ponieważ jednym z motto C ++ jest:
Nie płacisz za to, czego nie używasz
Z tego samego powodu,
operator[]
zvector
klasą nie sprawdza, czy indeks jest poza granicami, na przykład.źródło
Ze względów historycznych, głównie dlatego, że tak się to robi w C. Dlaczego tak się to robi w C, to inna kwestia, ale myślę, że zasada zerowego narzutu była w jakiś sposób zaangażowana w tę decyzję projektową.
źródło
Poza tym, mamy ostrzeżenie, kiedy to wyrzucisz: "jest prawdopodobnie używane przed przypisaniem wartości" lub podobna informacja, w zależności od twojego kompilatora.
Kompilujesz z ostrzeżeniami, prawda?
źródło
Niewiele jest sytuacji, w których niezainicjowanie zmiennej ma sens, a inicjalizacja domyślna ma niewielki koszt, więc po co to robić?
C ++ to nie C89. Do diabła, nawet C to nie C89. Możesz mieszać deklaracje i kod, więc powinieneś odłożyć deklarację do momentu, gdy będziesz mieć odpowiednią wartość do zainicjowania.
źródło
Wskaźnik to po prostu inny typ. Jeśli utworzyć
int
,char
lub dowolny inny typ POD nie jest inicjowany na zero, więc dlaczego wskaźnik? Może to zostać uznane za niepotrzebne obciążenie dla kogoś, kto pisze taki program.Jeśli wiesz, że zamierzasz go zainicjalizować, dlaczego program miałby ponosić koszty, gdy po raz pierwszy tworzysz
pBuf
w górnej części metody? To jest zasada zerowego narzutu.źródło
Jeśli potrzebujesz wskaźnika, który jest zawsze inicjalizowany na NULL, możesz użyć szablonu C ++ do emulacji tej funkcji:
źródło
Foo *a
, używaszInitializedPointer<Foo> a
- Ćwiczenie czysto akademickie, podobnie jakFoo *a=0
mniej pisania. Jednak powyższy kod jest bardzo przydatny z edukacyjnego punktu widzenia. Po niewielkiej modyfikacji (do „placeholding” ctor / dtor i assignment ops), można go łatwo rozszerzyć na różne typy inteligentnych wskaźników, w tym wskaźniki zakresów (które są wolne na destruktorze) i wskaźniki zliczane do odniesień, dodając inc / dec operacji, gdy m_pPointer jest ustawiony lub wyczyszczony.Zauważ, że dane statyczne są inicjowane na 0 (chyba że powiesz inaczej).
I tak, powinieneś zawsze deklarować swoje zmienne tak późno, jak to możliwe, z wartością początkową. Kod jak
powinien uruchomić dzwonek alarmowy, kiedy go czytasz. Nie wiem, czy da się przekonać jakieś kłaczki, żeby to oszukać, ponieważ jest to w 100% legalne.
źródło
Innym możliwym powodem jest to, że w momencie łącza wskaźniki otrzymują adres, ale za pośrednie adresowanie / usuwanie odniesień do wskaźnika odpowiada programista. Zwykle kompilator nie przejmuje się tym mniej, ale obciążenie jest przenoszone na programistę w celu zarządzania wskaźnikami i upewnienia się, że nie wystąpią wycieki pamięci.
W rzeczywistości, w skrócie, są one inicjowane w tym sensie, że w czasie łączenia zmiennej wskaźnikowej nadawany jest adres. W powyższym przykładowym kodzie gwarantuje to awarię lub wygenerowanie SIGSEGV.
Ze względu na rozsądek, zawsze inicjalizuj wskaźniki na NULL, w ten sposób, jeśli jakakolwiek próba wyodrębnienia go bez
malloc
lubnew
wskaże programiście powód, dla którego program źle się zachował.Mam nadzieję, że to pomoże i ma sens,
źródło
Cóż, gdyby C ++ zainicjował wskaźniki, ludzie C narzekający, że „C ++ jest wolniejsze niż C”, mieliby coś prawdziwego, na czym mogliby się oprzeć;)
źródło
C ++ wywodzi się z C - i jest kilka powodów, dla których wraca z tego:
C, nawet bardziej niż C ++, jest zamiennikiem języka asemblera. Nie robi niczego, czego mu nie każesz. Dlatego: Jeśli chcesz to ZEROWAĆ - zrób to!
Ponadto, jeśli wyzerujesz rzeczy w czystym języku, takim jak C, automatycznie pojawią się pytania dotyczące spójności: Jeśli coś zrobisz - czy powinno to zostać automatycznie wyzerowane? A co ze strukturą utworzoną na stosie? czy wszystkie bajty powinny być wyzerowane? A co ze zmiennymi globalnymi? co z instrukcją typu „(* 0x18)”; czy to nie znaczy, że pozycja pamięci 0x18 powinna być wyzerowana?
źródło
calloc()
.O jakich wskazówkach mówisz?
Dla bezpieczeństwa wyjątku, zawsze używaj
auto_ptr
,shared_ptr
,weak_ptr
a ich inne warianty.Cechą charakterystyczną dobrego kodu jest taki, który nie zawiera ani jednego wywołania
delete
.źródło
auto_ptr
i substituteunique_ptr
.O chłopie. Prawdziwą odpowiedzią jest to, że łatwo jest wyzerować pamięć, co jest podstawową inicjalizacją, powiedzmy, wskaźnika. Co również nie ma nic wspólnego z inicjalizacją samego obiektu.
Biorąc pod uwagę ostrzeżenia, które większość kompilatorów podaje na najwyższych poziomach, nie wyobrażam sobie programowania na najwyższym poziomie i traktowania ich jako błędów. Ponieważ ich włączenie nigdy nie uratowało mi ani jednego błędu w ogromnej ilości wyprodukowanego kodu, nie mogę tego polecić.
źródło
NULL
, że inicjowanie go jest tak samo błąd.