Jak ważna jest inicjalizacja zmiennej


9

Jak ważna jest inicjalizacja zmiennych?

Czy właściwa inicjalizacja pozwala uniknąć wycieków pamięci lub ma zalety związane z wydajnością?


14
To zależy od języka. W niektórych językach bardzo ważne jest zapobieganie błędom, w pozostałych jest to po prostu dobra rzecz, aby poprawić czytelność.
Telastyn

Dzięki Telastyn za Twój wkład. Czy potrafisz przedstawić przypadek, w którym staje się on ważny w zależności od języka?
Vivek

4
C ++ jest tutaj powszechnie znany. Podczas debugowania lokalne zmienne są inicjowane na 0 (lub null) przez popularne kompilatory, ale są losowymi śmieciami podczas kompilacji do wydania. (chociaż moja znajomość C ++ pochodzi z około 10 lat temu, sprawy mogły się zmienić)
Telastyn

To przypadek raz spalonego-dwukrotnie nieśmiałego. Ponieważ widziałem / miałem błędy spowodowane przez niezainicjowane zmienne, zwłaszcza wskaźniki, stało się to nawykiem. Jeśli chodzi o wydajność, zwykle nie ma to znaczenia. W przypadku wycieków pamięci nie jest to naprawdę problem.
Mike Dunlavey

1
@Telastyn jest gorzej. Niezdefiniowane zachowanie nie ogranicza się do stanu śmieci, wszystko może się zdarzyć. Kompilator może założyć, że ścieżki odczytujące niezainicjowane zmienne są nieosiągalne i eliminuje „niepowiązane” efekty, które pojawiają się po drodze.
Caleth,

Odpowiedzi:


7

Niezainicjowane zmienne powodują, że program nie jest deterministyczny. Za każdym razem, gdy program działa, może zachowywać się inaczej. Niepowiązane zmiany środowiska operacyjnego, pory dnia, fazy księżyca i permutacji takich wpływają na to, jak i kiedy manifestują się te demony. Program może uruchomić milion razy, zanim pojawi się defekt, mogą to zrobić za każdym razem lub uruchomić kolejny milion. Wiele problemów sprowadza się do „usterki” i są ignorowane lub raporty o błędach od klientów są zamykane jako „nie do odtworzenia”. Jak często restartowałeś komputer, aby „naprawić” problem? Jak często mówiłeś do klienta: „Nigdy nie widziałem, żeby tak się stało, daj mi znać, jeśli zobaczysz to ponownie” - mając nadzieję (wiedząc), że nie będzie!

Ponieważ odtworzenie wady może być prawie niemożliwe w środowisku testowym, prawie niemożliwe jest jej znalezienie i naprawienie.

Ujawnienie błędu może zająć lata, zwykle w kodzie uważanym za niezawodny i stabilny. Zakłada się, że defekt ma nowszy kod - jego odnalezienie może potrwać znacznie dłużej. Zmiana w kompilatorze, przełącznik kompilatora, a nawet dodanie wiersza kodu może zmienić zachowanie.

Inicjowanie zmiennych ma ogromną przewagę wydajności, nie tylko dlatego, że program, który działa poprawnie, jest nieskończenie szybszy niż ten, który tego nie robi, ale programiści spędzają mniej czasu na szukaniu i naprawianiu defektów, których nie powinno być, a więcej na wykonywaniu „prawdziwej” pracy.

Inną znaczącą zaletą inicjowania zmiennych jest to, że autor kodu musi zdecydować, na czym je zainicjować. Nie zawsze jest to trywialne ćwiczenie, a gdy nie jest trywialne, może wskazywać na zły projekt.

Wycieki pamięci to inny problem, ale właściwa inicjalizacja może nie tylko pomóc w ich zapobieganiu, ale może również pomóc w ich wykryciu i znalezieniu źródła - jest w dużej mierze zależne od języka i to naprawdę osobne pytanie warte dalszych badań, niż jestem w stanie udzielić w tej odpowiedzi.

Edycja: W niektórych językach (np. C #) nie można używać niezainicjowanych zmiennych, ponieważ program nie będzie się kompilował ani nie zgłosi błędu po uruchomieniu, jeśli zostanie wykonany. Jednak wiele języków o tych cechach posiada interfejsy do potencjalnie niebezpiecznego kodu, dlatego należy zachować ostrożność, używając takich interfejsów no, aby wprowadzić niezainicjowane zmienne.


6
Wiele języków programowania automatycznie ustawia zmienne na pewną predefiniowaną wartość, więc wiele z tego, co mówisz, nie ma zastosowania do tych języków.
Robert Harvey

2
Aby powtórzyć to, co powiedział @RobertHarvey, nic z tego nie dotyczy C #. Inicjowanie zmiennych podczas ich deklarowania nie ma żadnej przewagi wydajnościowej i nie można użyć niezainicjowanej zmiennej, więc nie można winić za to nie powtarzalnych błędów. (Możliwe jest użycie niezainicjowanego pola klasy, ale zostaje ono ustawione na wartość domyślną i generuje ostrzeżenie w tym przypadku)
Bobson,

4
@mattnz - Chodzi o to, że w przypadku języków, które zachowują się jak C # (lub Java), niektóre z tych porad są mylące lub wręcz błędne. Jako języka agnostyka pytanie, powinien on mieć agnostycznego reakcję językową, co oznacza adresowania języków, które dzieje uchwyt zainicjowany zmienne bezpiecznie, jak również te, które tego nie robią.
Bobson

1
Dodałbym również, że problem niezainicjowanej zmiennej nie jest trudny do znalezienia, ponieważ jakikolwiek pół przyzwoity kompilator / analizator statyczny ostrzeże przed nimi
jk.

1
W przypadku języka Java (i prawdopodobnie języka C #) przedwczesne inicjowanie plików lokalnych jest niepotrzebne i prawdopodobnie prowadzi do większej liczby błędów. Na przykład ustawienie zmiennej na wartość null przed przypisaniem jej warunkowo uniemożliwia kompilatorowi powiedzenie, że jedna ze ścieżek w kodzie może nie spowodować przypisania zmiennej.
JimmyJames

7

Inicjowanie zmiennej, jak wskazał Telastyn, może zapobiec błędom. Jeśli zmienna jest typem odniesienia, zainicjowanie jej może zapobiec błędom odniesienia o wartości zerowej w linii.

Zmienna dowolnego typu, która ma wartość inną niż null, zajmie trochę pamięci do przechowywania wartości domyślnej.


6

Próba użycia niezainicjowanej zmiennej jest zawsze błędem, więc sensowne jest zminimalizowanie prawdopodobieństwa wystąpienia tego błędu.

Prawdopodobnie najpopularniejszym podejściem języków programowania w celu złagodzenia problemu jest automatyczna inicjalizacja do wartości domyślnej, więc przynajmniej jeśli zapomnisz zainicjować zmienną, będzie to coś podobnego 0zamiast czegoś podobnego 0x16615c4b.

To rozwiązuje duży odsetek błędów, jeśli i tak potrzebujesz zmiennej zainicjowanej na zero. Jednak użycie zmiennej, która została zainicjowana na niepoprawną wartość, jest tak samo złe, jak użycie zmiennej, która nie została w ogóle zainicjowana. W rzeczywistości czasami może być nawet gorzej, ponieważ błąd może być bardziej subtelny i trudny do wykrycia.

Funkcjonalne języki programowania rozwiązują ten problem, nie tylko nie dopuszczając niezainicjowanych wartości, ale całkowicie uniemożliwiając ponowne przypisanie. To eliminuje problem i okazuje się, że nie jest tak surowym ograniczeniem, jak mogłoby się wydawać. Nawet w językach niefunkcjonalnych, jeśli czekasz na zadeklarowanie zmiennej, dopóki nie uzyskasz poprawnej wartości do jej zainicjowania, twój kod jest zwykle bardziej niezawodny.

Jeśli chodzi o wydajność, jest to prawdopodobnie nieistotne. W najgorszym przypadku z niezainicjowanymi zmiennymi masz jedno dodatkowe przypisanie i przywiązujesz trochę pamięci na dłużej niż to konieczne. Dobre kompilatory mogą optymalizować różnice w wielu przypadkach.

Wycieki pamięci są całkowicie niezwiązane, chociaż odpowiednio zainicjowane zmienne są w zasięgu przez krótszy okres czasu, a zatem może być nieco mniej prawdopodobne, że programista wycieknie.


Zawsze? Masz na myśli to, że „zawsze” jak w „Jak stała wiadomość Valgrind renderowała OpenSSL obok bezużytecznego” marc.info/?t=114651088900003&r=1&w=2 ? Czy masz na myśli drugie, „prawie zawsze” jedno?
JensG

1
Mogę wymyślić trzy języki, które dopuszczają niezainicjowane zmienne bez błędów, z których jeden używa takich do celów językowych.
DougM

Byłbym zainteresowany szczegółami. Podejrzewam, że w takich przypadkach zmienne nie są tak naprawdę niezainicjowane, ale są inicjowane w sposób inny niż bezpośrednio przez programistę w miejscu deklaracji. Lub przypisuje się je w jakiś pośredni sposób przed dereferencją.
Karl Bielefeldt

5

Inicjalizacja oznacza, że ​​wartość początkowa ma znaczenie. Jeśli wartość początkowa ma znaczenie, to tak, oczywiście musisz upewnić się, że została ona zainicjowana. Jeśli to nie ma znaczenia, oznacza to, że zostanie zainicjowane później.

Niepotrzebna inicjalizacja powoduje zmarnowane cykle procesora. Podczas gdy te zmarnowane cykle mogą nie mieć znaczenia w niektórych programach, w innych programach, każdy pojedynczy cykl jest ważny, ponieważ szybkość ma zasadnicze znaczenie. Dlatego bardzo ważne jest, aby zrozumieć, jakie są cele związane z wydajnością i czy należy zainicjować zmienne, czy nie.

Wycieki pamięci to zupełnie inna kwestia, która zazwyczaj wiąże się z funkcją alokatora pamięci w celu wydania i późniejszego przetworzenia bloków pamięci. Pomyśl o poczcie. Idź i poproś o skrzynkę pocztową. Dają ci jeden. Prosisz o kolejny. Dają ci jeszcze jeden. Zasadą jest, że kiedy skończysz używać skrzynki pocztowej, musisz ją zwrócić. Jeśli zapomnisz go oddać, nadal myślą, że go masz, a pudełko nie może być ponownie wykorzystane przez nikogo innego. Jest więc część pamięci związana i nieużywana, i to jest tak zwane wyciek pamięci. Jeśli nadal będziesz pytać o pola, zabraknie ci pamięci. Upraszczałem to, ale to jest podstawowa idea.


-1 redefiniujesz znaczenie inicjalizacji w tym kontekście.
Pieter B,

@Ieter B, nie rozumiem twojego komentarza. Proszę, jeśli powiesz, jak się czuję, „na nowo zdefiniuj, co oznacza inicjalizacja w tym kontekście”. Dziękuję
widok eliptyczny

Przeczytaj własne zdanie, jest to okrągłe rozumowanie: „Inicjalizacja oznacza, że ​​wartość początkowa ma znaczenie. Jeśli wartość początkowa ma znaczenie, to tak, oczywiście musisz upewnić się, że została ona zainicjowana. Jeśli to nie ma znaczenia, oznacza to, że otrzyma zainicjowane później ”.
Pieter B,

@ Pieter B, Niektóre osoby inicjują z reguły raczej niż z powodów programowych, tj. Inicjują, czy wartość początkowa ma znaczenie, czy nie. Czy to nie jest sedno OQ: Jak ważna jest inicjalizacja zmiennej? W każdym razie zostałeś tutaj głosowany.
Widok eliptyczny

2

Jak powiedzieli inni, zależy to od języka. Ale zademonstruję moje pomysły Java (i Effective Java) na temat inicjowania zmiennych. Powinny one być użyteczne w wielu innych językach wyższego poziomu.

Stałe i zmienne klasowe

Zmienne klasy - oznaczone staticw Javie - są jak stałe. Te zmienne powinny normalnie być ostateczne i inicjowane bezpośrednio po definicji przy użyciu =lub z poziomu bloku inicjalizującego klasę static { // initialize here }.

Pola

Podobnie jak w wielu językach wyższego poziomu i językach skryptowych automatycznie przypisana zostanie wartość domyślna. Dla liczb, a charbędzie to wartość zerowa. Tak będzie w przypadku ciągów znaków i innych obiektów null. Teraz nulljest niebezpieczny i powinien być używany oszczędnie. Dlatego te pola powinny zostać ustawione na prawidłową wartość tak szybko, jak to możliwe. Konstruktor jest zwykle idealnym miejscem do tego. Aby upewnić się, że zmienne są ustawione podczas konstruktora, a nie zmieniane później, możesz oznaczyć je finalsłowem kluczowym.

Spróbuj oprzeć się pokusie użycia nulljako jakiejś flagi lub specjalnej wartości. Lepiej np. Dołączyć określone pole do przechowywania stanu. Dobrym wyborem byłoby pole z nazwą, statektóra używa wartości Statewyliczenia.

Parametry metody

Ponieważ zmiany wartości parametrów (odniesienia do obiektów lub podstawowych typów, takich jak liczby całkowite itp.) Nie będą widoczne dla osoby wywołującej, parametry należy oznaczyć jako final. Oznacza to, że wartości samej zmiennej nie można zmienić. Zauważ, że wartość modyfikowalnych instancji obiektów może ulec zmianie, odwołanie nie może zostać zmieniony, aby wskazywał na inny obiekt lub nullchociaż.

Zmienne lokalne

Zmienne lokalne nie są automatycznie inicjowane; muszą zostać zainicjowane przed użyciem ich wartości. Jedną z metod upewnienia się, że zmienna została zainicjowana, jest bezpośrednia inicjalizacja jakiejś wartości domyślnej. Jest jednak coś, co powinno nie robić. Przez większość czasu wartość domyślna nie jest wartością, której można oczekiwać.

O wiele lepiej jest definiować zmienną dokładnie tam, gdzie jej potrzebujesz. Jeśli zmienna ma przyjmować tylko jedną wartość (co jest prawdziwe w przypadku większości zmiennych w dobrym kodzie), możesz ją oznaczyć final. Dzięki temu zmienna lokalna jest przypisywana dokładnie raz, a nie zero razy lub dwa razy. Przykład:

public static doMethod(final int x) {
    final int y; // no assignment yet, it's final so it *must* be assigned
    if (x < 0) {
        y = 0;
    } else if (x > 0) {
        y = x;
    } else {
        // do nothing <- error, y not assigned if x = 0
        // throwing an exception here is acceptable though
    }
}

Pamiętaj, że wiele języków ostrzeże Cię, jeśli zmienna pozostanie niezainicjowana przed użyciem. Sprawdź specyfikacje językowe i fora, aby sprawdzić, czy nie musisz się niepotrzebnie martwić.


1

Nie ma problemu z niezainicjowaniem zmiennych.

Problem pojawia się tylko wtedy, gdy czytasz zmienną, która nie została jeszcze zapisana.

W zależności od kompilatora i / lub rodzaju zmiennej inicjalizacja jest wykonywana podczas uruchamiania aplikacji. Albo nie.

Powszechnym zastosowaniem jest nie poleganie na automatycznej inicjalizacji.


0

Inicjowanie zmiennych (pośrednio lub jawnie) ma kluczowe znaczenie. Brak inicjalizacji zmiennej jest zawsze błędem (mogą być jednak inicjowane niejawnie. Patrz poniżej). Nowoczesne kompilatory, takie jak kompilator C # (jako przykład) traktują to jako błąd i nie pozwalają na wykonanie kodu. Niezainicjowana zmienna jest po prostu bezużyteczna i szkodliwa. O ile nie tworzysz generatora liczb losowych, oczekujesz, że fragment kodu da deterministyczny i powtarzalny wynik. Można to osiągnąć tylko wtedy, gdy zaczniesz pracować z zainicjowanymi zmiennymi.

Naprawdę interesujące jest pytanie, czy zmienna jest inicjowana automatycznie, czy też trzeba to zrobić ręcznie. To zależy od używanego języka. Na przykład w języku C # pola, tj. „Zmienne” na poziomie klasy, są zawsze automatycznie inicjowane do wartości domyślnej dla tego typu zmiennej default(T). Ta wartość odpowiada wzorowi bitowemu złożonemu ze wszystkich zer. Jest to część specyfikacji języka, a nie tylko techniczny szczegół implementacji języka. Dlatego możesz bezpiecznie na nim polegać. Bezpiecznie jest nie inicjować zmiennej jawnie, jeśli (i tylko jeśli) specyfikacja języka stwierdza, że ​​jest ona inicjowana niejawnie.Jeśli potrzebujesz innej wartości, musisz jawnie zainicjować zmienną. Jednak; w C # zmienne lokalne, tj. zmienne zadeklarowane w metodach, nie są inicjowane automatycznie i zawsze należy jawnie zainicjować zmienną.


2
nie jest to pytanie specyficzne dla C #.
DougM

@DougM: Wiem. To nie jest odpowiedź specyficzna dla C #, właśnie wziąłem C # jako przykład.
Olivier Jacot-Descombes

Nie wszystkie języki wymagają jawnej inicjalizacji zmiennych. Twoje stwierdzenie „brak inicjalizacji jest zawsze błędem” jest fałszywe i nie dodaje żadnej jasności do pytania. możesz zrewidować swoją odpowiedź.
DougM

@DougM: Czy nadzorowałeś moje zdanie „Naprawdę interesujące pytanie dotyczy tego, czy zmienna jest inicjowana automatycznie, czy też musisz to zrobić ręcznie.”?
Olivier Jacot-Descombes

masz na myśli tego zakopanego w połowie akapitu? tak. Powinieneś był uczynić go bardziej widocznym i dodać kwalifikator do swojego „zawsze” roszczenia.
DougM
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.