Gdzie są przechowywane wartości zerowe, czy w ogóle są przechowywane?


39

Chcę dowiedzieć się o wartościach zerowych lub referencjach zerowych.

Na przykład mam klasę o nazwie Apple i utworzyłem jej instancję.

Apple myApple = new Apple("yummy"); // The data is stored in memory

Potem zjadłem to jabłko i teraz musi być zerowe, więc ustawiłem je jako zerowe.

myApple = null;

Po tym telefonie zapomniałem, że go zjadłem i chcę teraz sprawdzić.

bool isEaten = (myApple == null);

Gdzie w tym wywołaniu odwołuje się myApple? Czy null jest specjalną wartością wskaźnika? Jeśli tak, jeśli mam 1000 pustych obiektów, to czy zajmują one 1000 miejsc pamięci obiektów czy 1000 miejsc pamięci int, jeśli uważamy, że typ wskaźnika to int?

Odpowiedzi:


45

W twoim przykładzie myApplema specjalną wartość null(zwykle wszystkie bity zerowe), a więc nie odnosi się do niczego. Obiekt, do którego pierwotnie się odnosił, został teraz utracony na stercie. Nie ma możliwości odzyskania jego lokalizacji. Jest to znane jako wyciek pamięci w systemach bez wyrzucania elementów bezużytecznych.

Jeśli pierwotnie ustawiłeś 1000 referencji na null, to masz miejsce tylko na 1000 referencji, zwykle 1000 * 4 bajtów (w systemie 32-bitowym, dwa razy więcej niż w 64). Jeśli te 1000 odniesień pierwotnie wskazywało na rzeczywiste obiekty, to przydzielono 1000 razy rozmiar każdego obiektu plus miejsce na 1000 odniesień.

W niektórych językach (takich jak C i C ++) wskaźniki zawsze wskazują na coś, nawet gdy są „niezainicjowane”. Problem polega na tym, czy adres, do którego prowadzą, jest legalny dla twojego programu. Specjalny adres zero (aka null) celowo nie jest odwzorowywany w przestrzeni adresowej, więc jednostka zarządzania pamięcią (MMU) generuje błąd segmentacji, gdy jest uzyskiwany do niego dostęp i następuje awaria programu. Ale ponieważ adres zero celowo nie odwzorowywane, staje się wartością idealny do wykorzystania w celu wskazania, że wskaźnik nie jest skierowany do niczego, stąd swoją rolę null. Aby ukończyć historię, alokując pamięć za pomocą newlubmalloc(), system operacyjny konfiguruje MMU do mapowania stron pamięci RAM w przestrzeni adresowej i stają się one użyteczne. Nadal istnieją zwykle duże zakresy przestrzeni adresowej, które nie są mapowane, a zatem prowadzą również do błędów segmentacji.


Bardzo dobre wytłumaczenie.
NoChance

6
Jest to nieco błędne w części „wyciek pamięci”. Jest to wyciek pamięci w systemach bez automatycznego zarządzania pamięcią. Jednak GC nie jest jedynym możliwym sposobem realizacji automatycznego zarządzania pamięcią. C ++ std::shared_ptr<Apple>jest przykładem, który nie jest ani GC, ani nie przecieka Applepo wyzerowaniu.
MSalters

1
@MSalters - Czy nie jest to shared_ptrtylko podstawowa forma zbierania śmieci? GC nie wymaga oddzielnego „modułu wyrzucania elementów bezużytecznych”, tylko to, że ma miejsce wyrzucanie elementów bezużytecznych.
Przywróć Monikę

5
@Brendan: Pojęcie „odśmiecanie” jest prawie powszechnie rozumiane jako odnoszące się do niedeterministycznego zbierania, które odbywa się niezależnie od normalnej ścieżki kodu. Deterministyczne zniszczenie oparte na liczeniu referencji jest czymś zupełnie innym.
Mason Wheeler,

1
Dobre wytłumaczenie. Jednym z nieco mylących punktów jest założenie, że alokacja pamięci jest mapowana na pamięć RAM. RAM jest jednym mechanizmem do krótkotrwałego przechowywania pamięci, ale rzeczywisty mechanizm przechowywania jest abstrakcyjny przez system operacyjny. W systemie Windows (w przypadku aplikacji z zerowym dzwonieniem) strony pamięci są zwirtualizowane i mogą być mapowane na pamięć RAM, plik wymiany dysku lub inne urządzenie pamięci masowej.
Simon Gillbee

13

Odpowiedź zależy od używanego języka.

C / C ++

W C i C ++ słowem kluczowym było NULL, a tak naprawdę NULL było 0. Zdecydowano, że „0x0000” nigdy nie będzie prawidłowym wskaźnikiem do obiektu, a więc taka wartość jest przypisywana w celu wskazania, że ​​to nie jest prawidłowym wskaźnikiem. Jest to jednak całkowicie arbitralne. Jeśli spróbujesz uzyskać do niego dostęp jak wskaźnik, zachowa się on dokładnie tak jak wskaźnik do obiektu, którego już nie ma w pamięci, powodując zgłoszenie wyjątku nieprawidłowego wskaźnika. Sam wskaźnik zajmuje pamięć, ale nie więcej niż zwykły obiekt. Dlatego jeśli masz 1000 zerowych wskaźników, jest to odpowiednik 1000 liczb całkowitych. Jeśli niektóre z tych wskaźników wskazują na prawidłowe obiekty, wówczas użycie pamięci byłoby równoważne 1000 liczb całkowitych plus pamięć zawarta w tych prawidłowych wskaźnikach. Pamiętaj, że w C lub C ++,nie oznacza, że ​​pamięć została zwolniona, więc musisz jawnie usunąć ten obiekt za pomocą dealloc (C) lub delete (C ++).

Jawa

W przeciwieństwie do C i C ++, w Javie null jest jedynie słowem kluczowym. Zamiast zarządzać wartością zerową jak wskaźnikiem do obiektu, jest on zarządzany wewnętrznie i traktowany jak literał. To wyeliminowało potrzebę wiązania wskaźników jako typów całkowitych i pozwala Javie całkowicie wyodrębnić wskaźniki. Jednak nawet jeśli Java lepiej to ukrywa, nadal są wskaźnikami, co oznacza, że ​​1000 wskaźników zerowych nadal zużywa równowartość 1000 liczb całkowitych. Oczywiście, gdy wskazują na obiekty, podobnie jak C i C ++, pamięć jest zużywana przez te obiekty, dopóki nie odwołują się do nich żadne wskaźniki, jednak w przeciwieństwie do C i C ++, śmieciarz zbiera je przy następnym przejściu i uwalnia pamięć, bez konieczności śledzenia, które obiekty są zwalniane, a które nie, w większości przypadków (chyba że masz powody, aby na przykład słabo odwoływać się do obiektów).


9
Twoje rozróżnienie nie jest poprawne: w rzeczywistości w C i C ++ wskaźnik zerowy wcale nie musi wskazywać na adres pamięci 0 (chociaż jest to naturalna implementacja, tak samo jak w Javie i C #). Może wskazywać dosłownie wszędzie. Jest to nieco zakłócone przez fakt, że literał-0 można niejawnie przekonwertować na wskaźnik zerowy. Ale wzorzec bitów przechowywany dla wskaźnika zerowego nadal nie musi zawierać samych zer.
Konrad Rudolph

1
Nie, mylisz się. Semantyka jest całkowicie przezroczysta… w programie wskaźniki zerowe i makro NULL( nawiasem mówiąc, nie słowo kluczowe) są traktowane tak, jakby były zerowymi bitami. Ale nie muszą być realizowane jako takie, w rzeczywistości niektóre niejasne implementacje zrobić użytku niezerowe wskaźniki null. Jeśli napiszę, if (myptr == 0)kompilator zrobi właściwą rzecz, nawet jeśli wskaźnik zerowy jest wewnętrznie reprezentowany przez 0xabcdef.
Konrad Rudolph

3
@ Neil: stała zerowego wskaźnika (wartość typu liczb całkowitych, której wynikiem jest zero) można przekształcić w wartość zerowego wskaźnika . (§4.10 C ++ 11.) Nie ma gwarancji, że zerowa wartość wskaźnika ma wszystkie bity równe zero. 0jest stałą wskaźnika zerowego, ale nie oznacza to, że myptr == 0sprawdza, czy wszystkie bity myptrsą równe zero.
Mat

5
@Neil: Możesz sprawdzić ten wpis w odpowiedzi na
najczęściej

1
@Neil Dlatego zadałem sobie trud, żeby w ogóle nie wspominać o NULLmakrze, raczej mówiąc o „wskaźniku zerowym” i wyraźnie wspominając, że „literał-0 można niejawnie przekonwertować na wskaźnik zerowy”.
Konrad Rudolph

5

Wskaźnik jest po prostu zmienną, która jest przeważnie liczbą całkowitą. Określa adres pamięci, w którym przechowywany jest rzeczywisty obiekt.

Większość języków pozwala na dostęp do elementów obiektu za pomocą tej zmiennej wskaźnika:

int localInt = myApple.appleInt;

Kompilator wie, jak uzyskać dostęp do członków Apple. „Podąża” za wskaźnikiem do myAppleadresu i pobiera wartośćappleInt

Jeśli przypiszesz wskaźnik zerowy do zmiennej wskaźnika, ustawisz wskaźnik na brak adresu pamięci. (Co uniemożliwia dostęp do konta).

Dla każdego wskaźnika potrzebna jest pamięć do przechowywania wartości liczby całkowitej adresu pamięci (głównie 4 bajty w systemach 32-bitowych, 8 bajtów w systemach 64-bitowych). Dotyczy to również wskaźników zerowych.


Myślę, że zmienne / obiekty odniesienia nie są dokładnie wskaźnikami. Jeśli je wydrukujesz, będą zawierać ClassName @ Hashcode. JVM wewnętrznie używa Hashtable do przechowywania Hashcode z faktycznym adresem i używa Algorytmu Hash do pobierania faktycznego adresu, gdy jest to konieczne.
minusSeven

@minusSeven To prawda, co dotyczy dosłownych obiektów, takich jak liczby całkowite. W przeciwnym razie tablica skrótów zawiera wskaźniki do innych obiektów zawartych w samej klasie Apple.
Neil

@minusSeven: Zgadzam się. Szczegóły implementacji wskaźnika zależą w dużej mierze od języka / środowiska wykonawczego. Myślę jednak, że te szczegóły nie są tak istotne dla konkretnego pytania.
Stephan

4

Szybki przykład (nazwy zmiennych nie są przechowywane):

void main()
{
  int X = 3;
  int *Y = X;
  int *Z = null;
} // void main(...)


...........................
....+-----+--------+.......
....|     |   X    |.......
....+-----+--------+.......
....| 100 |   3    |<---+..
....+-----+--------+....|..
........................|..
....+-----+--------+....|..
....|     |   Y    |....|..
....+-----+--------+....|..
....| 102 |  100   +----+..
....+-----+--------+.......
...........................
....+-----+--------+.......
....|     |   z    |.......
....+-----+--------+.......
....| 104 |   0    |.......
....+-----+--------+.......
...........................

Twoje zdrowie.

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.