Оператор new ініціалізує пам'ять до нуля


79

Є такий код:

#include <iostream>

int main(){
  unsigned int* wsk2 = new unsigned int(5);
  std::cout << "wsk2: " << wsk2 << " " << *wsk2 << std::endl;
  delete wsk2;
  wsk2 = new unsigned int;
  std::cout << "wsk2: " << wsk2 << " " << *wsk2 << std::endl;
  return 0;
}

Результат:

wsk2: 0x928e008 5
wsk2: 0x928e008 0

Я читав, що newне ініціалізує пам'ять нулями. Але тут здається, що це так. Як це працює?


2
Це не так, вам доведеться використовувати новий непідписаний int (). Читання неініціалізованих змінних - UB, 0, безумовно, можливо.
Hans Passant,

1
Окрім того, на практиці багато пам’яті в будь-якому випадку дорівнює нулю. Якщо ви робите звичайний malloc з великою частиною пам’яті, в одних системах часто все дорівнює нулю, а в інших - переважно.
Ніколас Вілсон,

2
@NicholasWilson: Для більшості систем це справедливо лише тоді, коли пам’ять надходить безпосередньо з ОС (з міркувань безпеки). Коли воно надходить із пам’яті, кешованої розподільником, воно, швидше за все, містить все, що робив до того, як звільнився () Це також причина, чому такі помилки іноді виникають не в тестових випадках / модульних тестах, а лише через деякий час запуску "реальної" програми. Вальгрінд тут на допомогу.
PlasmaHH

Так. Це свого роду міркування. Якщо ви зробите нову змінну і побачите, що це нуль, ви не можете відразу припустити, що щось у вашій програмі встановило її на нуль. Оскільки більшість пам’яті поставляється готовою до нуля, вона, ймовірно, все ще не ініціалізована. Я думаю, що моє зауваження насправді ближче до відповіді на запитання, ніж більшість відповідей нижче, оскільки допитувач хотів знати, чому неініціалізована пам’ять може бути нульовою.
Ніколас Вільсон

Спробуйте розподілити масив, встановивши для всіх елементів якесь ненульове значення, потім видаліть [] і розподіліть його знову - лише перший елемент буде обнулений - інші зберігають свої значення - що, на мою думку, є особливістю розподілювача пам'яті (перевірено під Linux ).
kyku

Відповіді:


203

Є дві версії:

wsk = new unsigned int;      // default initialized (ie nothing happens)
wsk = new unsigned int();    // zero    initialized (ie set to 0)

Також працює для масивів:

wsa = new unsigned int[5];   // default initialized (ie nothing happens)
wsa = new unsigned int[5](); // zero    initialized (ie all elements set to 0)

У відповідь на коментар нижче.

Ем ... ти впевнений, що new unsigned int[5]()цілі числа обнуляє?

Мабуть, так:

[C ++ 11: 5.3.4 / 15]: Новий вираз, який створює об’єкт типу T, ініціалізує цей об’єкт наступним чином: Якщо новий ініціалізатор опущений, об’єкт ініціалізується за замовчуванням (8.5); якщо ініціалізація не виконується, об'єкт має невизначене значення. В іншому випадку новий ініціалізатор інтерпретується відповідно до правил ініціалізації 8.5 для прямої ініціалізації.

#include <new>
#include <iostream>


int main()
{
    unsigned int   wsa[5] = {1,2,3,4,5};

    // Use placement new (to use a know piece of memory).
    // In the way described above.
    // 
    unsigned int*    wsp = new (wsa) unsigned int[5]();

    std::cout << wsa[0] << "\n";   // If these are zero then it worked as described.
    std::cout << wsa[1] << "\n";   // If they contain the numbers 1 - 5 then it failed.
    std::cout << wsa[2] << "\n";
    std::cout << wsa[3] << "\n";
    std::cout << wsa[4] << "\n";
}

Результати:

> g++ --version
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
Apple LLVM version 5.1 (clang-503.0.40) (based on LLVM 3.4svn)
Target: x86_64-apple-darwin13.2.0
Thread model: posix
> g++ t.cpp
> ./a.out
0
0
0
0
0
>

А як щодо перевантажених new/new[]операторів? Чи буде new unsigned int[5]()все одно нульова ініціалізація масиву, навіть якщо він має власну, абсолютно не пов’язану реалізацію?
SomeWittyUsername

2
@icepack: Функція operator new, визначена користувачем (розробником), вона може робити все, що їй подобається. Стандарт не обмежує функціонування цієї функції. Так само, як operator==може робити все, що визначає користувач (для цього навіть не потрібно повертати bool, не кажучи вже про тестування на рівність (хоча це погана форма, якщо цього не відбувається)).
Мартін Йорк,

1
Дякую. Щоб було зрозуміло: реалізація мого власного розподільника не має нічого спільного з нульовою ініціалізацією, і у випадку використання ()синтаксису буфер буде нульово ініціалізованим без будь-якого відношення до типу виділеного об’єкта та його конструкторів? (І я вважаю, що C ++ 11 у цьому аспекті нічого не змінює)
SomeWittyUsername

1
Ем ... ти впевнений, що new unsigned int[5]()цілі числа обнуляє?
Гонки легкості на орбіті

1
@ J3STER Передача вказівника з новим викликом називається розміщенням нового. Див 8.3.4 New. Чому його модифікували. Тому що ми змушуємо ініціалізувати значення. Див 11.6 Initializers. Зверніть увагу, ми передаємо вказівник на новий оператор. Значення wsaНЕ змінюється, змінюється адреса, на яку вказується.
Мартін Йорк

21

operator newне гарантується ініціалізація пам’яті ні до чого, а вираз new, який виділяє a unsigned intбез new-ініціалізатора, залишає об’єкт невизначеним значенням.

Зчитування значення неініціалізованого об’єкта призводить до невизначеної поведінки . Невизначена поведінка включає обчислення до нульового значення без побічних ефектів, але може призвести до чогось, тому вам слід уникати його спричинення.

У C ++ 11 використовується мова, що виділені об'єкти ініціалізуються за замовчуванням, що для некласових типів означає, що ініціалізація не виконується. Це відрізняється від значення ініціалізованого за замовчуванням у C ++ 03.


4

У деяких компіляторах версія налагодження new ініціалізує дані, але, звичайно, немає нічого, на що можна покластися.

Можливо також, що пам'ять щойно мала 0 за попереднє використання. Не припускайте, що з пам’яттю між видаленням та новим нічого не сталося. У фоновому режимі може бути щось зроблене, чого ви ніколи не помічали. Крім того, одне і те ж значення вказівника може бути не однаковою фізичною пам’яттю. Сторінки пам’яті переміщуються та виводяться на сторінку. Вказівник може бути відображений у зовсім іншому місці, ніж раніше.

Підсумок: якщо ви спеціально не ініціалізували розташування пам’яті, ви нічого не можете припустити щодо його вмісту. Менеджер пам’яті може навіть не виділити конкретне місце фізичної пам’яті, поки ви не використаєте пам’ять.

Сучасне управління пам’яттю надзвичайно складне, але як програміст на C ++ вам насправді все одно (переважно ‡). Грайте за правилами, і ви не потрапите в халепу.

‡ Вас може зацікавити оптимізація для зменшення кількості несправностей сторінки.


3

Це не так operator new, це newоператор. Насправді велика різниця! Різниця полягає в тому, що operator newце функція, яка повертає необроблену пам’ять; коли ви використовуєте newоператор, він викликає для вас конструктор. Це конструктор, який встановлює значення цього int, а не operator new.


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