Де зберігаються нульові значення чи вони взагалі зберігаються?


39

Я хочу дізнатися про нульові значення або нульові посилання.

Наприклад, у мене є клас під назвою Apple, і я створив його примірник.

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

Тоді я з’їв це яблуко, і тепер воно повинно бути нульовим, тому я встановив його як нульове.

myApple = null;

Після цього дзвінка я забув, що з’їв його і тепер хочу перевірити.

bool isEaten = (myApple == null);

Звідки в цьому дзвінку, де посилається myApple? Чи є null спеціальне значення вказівника? Якщо так, якщо у мене є 1000 нульових об'єктів, чи займають вони 1000 пам'яті об'єкта або 1000 пам'яті, якщо ми вважаємо тип вказівника як int?

Відповіді:


45

У вашому прикладі myAppleє особливе значення null(як правило, всі нульові біти), і тому нічого не посилається. Об'єкт, про який спочатку йшлося, зараз втрачається на купі. Немає можливості отримати його місцезнаходження. Це відомо як витік пам'яті в системах без збору сміття.

Якщо ви спочатку встановили 1000 посилань на null, то у вас є просто місце для 1000 посилань, як правило, 1000 * 4 байти (у 32-бітовій системі, вдвічі більше, ніж у 64). Якщо ці 1000 посилань спочатку вказували на реальні об'єкти, то ви виділили в 1000 разів більший розмір кожного об'єкта, плюс простір для 1000 посилань.

У деяких мовах (наприклад, C і C ++) вказівники завжди вказують на щось, навіть коли "неініціалізоване". Питання полягає в тому, чи законна у них адреса доступна для вашої програми. Спеціальна нульова адреса (ака null) навмисно не відображається у вашому адресному просторі, тому помилка сегментації генерується блоком управління пам'яттю (MMU), коли вона отримує доступ, і ваша програма виходить з ладу. Але оскільки нуль адреси навмисно не відображається, він стає ідеальним значенням для використання, щоб вказати, що вказівник не вказує ні на що, звідси і його роль null. Щоб завершити розповідь, виділяючи пам'ять за допомогою newабоmalloc(), операційна система налаштовує MMU для відображення сторінок оперативної пам’яті у вашому адресному просторі, і вони стають доступними для використання. Типово все ще існує широкий діапазон адресного простору, який не відображається, і, таким чином, призводить до помилок сегментації.


Дуже хороше пояснення.
NoChance

6
Це трохи неправильно в частині "витоку пам'яті". Це витік пам'яті в системах без автоматичного управління пам'яттю. Однак GC - не єдиний можливий спосіб впровадження автоматичного управління пам'яттю. std::shared_ptr<Apple>Приклад C ++ - це приклад, який не є ні GC, ні протікає Appleпри нульовому рівні.
MSalters

1
@MSalters - Це не shared_ptrпросто основна форма збору сміття? GC не вимагає існування окремого "сміттєзбірника", лише відбувається вивезення сміття.
Відновіть Моніку

5
@Brendan: Термін "збирання сміття" майже універсально позначається як недетермінований збір, який відбувається незалежно від звичайного шляху коду. Детерміновані руйнування, засновані на підрахунку еталонів, - це щось зовсім інше.
Мейсон Уілер

1
Гарне пояснення. Одним злегка оманливим моментом є припущення, що розподіл пам’яті відображає оперативну пам’ять. Оперативна пам’ять - це один із механізмів зберігання короткочасної пам’яті, але фактичний механізм зберігання абстрагується ОС. У Windows (для додатків без кільця) сторінки пам'яті віртуалізуються і можуть відображатись в оперативній пам'яті, файлі підкачки диска або, можливо, іншому пристрої зберігання даних.
Саймон Гіллбі

13

Відповідь залежить від мови, якою ви користуєтесь.

C / C ++

У C і C ++ ключове слово було NULL, а насправді NULL було 0. Було вирішено, що "0x0000" ніколи не буде дійсним покажчиком на об'єкт, і таким чином, це значення, яке присвоюється, вказує на те, що це не є дійсним покажчиком. Однак це абсолютно довільно. Якщо ви спробували отримати доступ до нього як вказівник, він поводитиметься точно так само, як вказівник на об’єкт, якого більше немає в пам'яті, викликаючи викидання недійсного винятку вказівника. Сам вказівник займає пам'ять, але не більше, ніж цілий об'єкт. Отже, якщо у вас є 1000 нульових покажчиків, це еквівалент 1000 цілих чисел. Якщо деякі з цих покажчиків вказують на дійсні об’єкти, то використання пам'яті було б еквівалентно 1000 цілих чисел плюс пам'ять, що міститься в цих дійсних покажчиках. Пам'ятайте, що в C або C ++,не означає, що пам'ять була звільнена, тому ви повинні явно видалити цей об'єкт за допомогою dealloc (C) або delete (C ++).

Java

На відміну від C і C ++, у Java null є лише ключовим словом. Замість того, щоб керувати null, як вказівник на об'єкт, він управляється внутрішньо і обробляється як буквальний. Це усунуло необхідність прив'язувати покажчики як цілі типи і дозволяє Java повністю абстрагувати вказівники. Однак навіть якщо Java приховує це краще, вони все ще є покажчиками, тобто 1000 нульових покажчиків все ще споживає еквівалент 1000 цілих чисел. Очевидно, що коли вони вказують на об'єкти, подібно до C і C ++, ці об’єкти споживають пам'ять, поки більше покажчики не посилаються на них, однак на відміну від C і C ++, збирач сміття підбирає його на наступному проході і звільняє пам'ять, не вимагаючи, щоб вам слід було відслідковувати, які об’єкти звільняються, а які - ні, в більшості випадків (якщо, наприклад, у вас немає причин слабо посилатися на об'єкти).


9
Ваша відмінність не є правильною: насправді в C і C ++ нульовий вказівник зовсім не повинен вказувати на адресу пам'яті 0 (хоча це природна реалізація, така ж, як у Java та C #). Він може вказувати буквально куди завгодно. Це трохи збентежено тим фактом, що буквально-0 можна неявно перетворити на нульовий покажчик. Але бітовий візерунок, збережений для нульового вказівника, все ж не повинен бути всіма нулями.
Конрад Рудольф

1
Ні, ви помиляєтесь. Семантика є повністю прозорою… У програмі нульові вказівники та макрос NULL( до речі, не ключове слово) трактуються так, ніби вони дорівнюють нулю. Але вони не повинні бути реалізовані як такі, а насправді деякі неясні реалізації роблять використання ненульових покажчиків невизначеними. Якщо я напишу, if (myptr == 0)тоді компілятор зробить правильну справу, навіть якщо нульовий покажчик внутрішньо представлений символом 0xabcdef.
Конрад Рудольф

3
@Neil: константа нульового покажчика (перше значення цілочисельного типу, що дорівнює нулю), може бути конвертована в значення нульового вказівника . (§4.10 C ++ 11.) За нульовим значенням вказівника не гарантується, що всі біти дорівнюють нулю. 0- константа нульового покажчика, але це не означає, що myptr == 0перевіряється, чи всі біти myptrдорівнюють нулю.
Мат

5
@Neil: Ви можете перевірити цей запис у C faq або це питання SO
hugomg

1
@Neil Ось чому я взяв болі, не кажучи про NULLмакрос взагалі, скоріше кажучи про «нульовий покажчик», і явно згадуючи, що «буквальний-0 може бути неявно перетворений в нульовий покажчик».
Конрад Рудольф

5

Вказівник - це просто змінна, яка в основному має цілочисельний тип. Він визначає адресу пам'яті, де зберігається фактичний об'єкт.

Більшість мов дозволяють отримати доступ до членів об'єкта за допомогою цієї змінної вказівника:

int localInt = myApple.appleInt;

Компілятор знає, як отримати доступ до членів Apple. Він "слідує" за вказівником на myApple's адресу та отримує значенняappleInt

Якщо ви призначите нульовий вказівник змінній вказівника, ви вказуєте на відсутність адреси пам'яті. (Що робить доступ учасника неможливим.)

Для кожного вказівника вам потрібна пам'ять, щоб утримувати ціле значення адреси пам'яті (в основному це 4 байти в 32-бітових системах, 8 байт у 64-бітових системах). Це справедливо і для нульових покажчиків.


Я думаю, що опорні змінні / об'єкти не є точно вказівниками. Якщо ви друкуєте їх, вони містять ClassName @ Hashcode. JVM внутрішньо використовує Hashtable для зберігання Hashcode з фактичною адресою та використовує алгоритм Hash для отримання фактичної адреси при необхідності.
мінусседем

@minusSeven Це правильно, що стосується буквальних об'єктів, таких як цілі числа. Інакше хештел містить покажчики на інші об'єкти, що містяться в самому класі Apple.
Ніл

@minusSeven: Я згоден. Деталі реалізації покажчика сильно залежать від мови / часу виконання. Але я думаю, що ці деталі не є такими актуальними для конкретного питання.
Стефан

4

Короткий приклад (імена змінних приміток не зберігаються):

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


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

Ура.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.