C ++ new int [0] - чи буде вона виділяти пам'ять?


241

Простий тестовий додаток:

cout << new int[0] << endl;

Виходи:

0x876c0b8

Так виглядає, що це працює. Що говорить про це стандарт? Чи завжди законно "виділяти" порожній блок пам'яті?


4
+1 Дуже цікаве запитання - хоча я не впевнений, наскільки це має значення в реальному коді.
Зіфре

37
@ Zifre: Я прошу цікавості, але це може мати значення в реальному світі, наприклад, коли розмір виділених блоків пам'яті обчислюється якимось чином, а результат обчислення може бути нульовим, тоді прямої необхідності додавати виключення немає не виділяти блоки нульового розміру .. Тому що вони повинні бути розподілені та видалені без помилок (якщо тільки блок нульового розміру не буде відмежований). Тож загалом це дає більш широку абстракцію того, що таке блок пам'яті.

2
@ emg-2: У вашому прикладі ситуації це насправді не має значення, оскільки видалення [] цілком легально на покажчику NULL :-).
Еван Теран

2
Це стосується лише тангенціально - тому я коментую тут - але C ++ багато в чому забезпечує, що окремі об’єкти мають унікальні адреси ... навіть якщо вони не потребують явного зберігання. Пов'язаним експериментом було б перевірити розмір порожньої структури. Або масив цієї структури.
Дрю Дорман

2
Для детального пояснення коментаря Шмоопті: Особливо, коли програмування з шаблонами (наприклад, шаблони класів політики, такі як std :: allocator), звичайно в C ++ є об'єкти нульового розміру. Узагальнюючий код може потребувати динамічного розподілу таких об'єктів та використання покажчиків на них для порівняння ідентичності об'єкта. Ось чому оператор new () повертає унікальні покажчики на запити нульового розміру. Незважаючи на те, що вони менш важливі / поширені, однакові міркування застосовуються до розподілу масиву та нового оператора [] ().
Тревор Робінсон

Відповіді:


233

З 5.3.4 / 7

Коли значення виразу у прямому новому деклараторі дорівнює нулю, функція розподілу викликається для виділення масиву без елементів.

З 3.7.3.1/2

Ефект перенаправлення покажчика, повернутого як запит на нульовий розмір, не визначений.

Також

Навіть якщо розмір запитуваного простору [по новому] дорівнює нулю, запит може не виконати.

Це означає, що ви можете це зробити, але ви не можете на законних підставах (у чітко визначеному порядку на всіх платформах) знецінювати отриману пам’ять - ви можете передати її лише для видалення масиву - і ви повинні її видалити.

Ось цікава примітка (тобто не нормативна частина стандарту, але включена в цілі опису), що додається до речення від 3.7.3.1/2

[32. Намір полягає в тому, щоб оператор new () був реалізований за допомогою виклику malloc () або calloc (), тому правила практично однакові. C ++ відрізняється від C тим, що вимагає нульового запиту повернути ненульовий покажчик.]


Якщо я не видаляю, то у мене витік пам'яті. Чи очікується? Принаймні, я не сподівався.
EralpB

12
@EralpB: навпаки, навіть якщо ваш запит дорівнює нулю, цей розподіл відбувається на Купі, де один запит передбачає кілька операцій з ведення книги, як, наприклад, розподілення та ініціалізація вартів кучі до та після зони, заданої алокатором, вставлення у вільний список або інші складні жахливі структури. Звільнити це означає вести відсталу бухгалтерію.
v.oddou

3
@EralpB так, я думаю, ви можете очікувати витоку пам'яті кожного разу, коли ви не врівноважуєте new[]a delete[]- незалежно від розміру. Зокрема, під час дзвінка new[i]вам потрібно трохи більше пам’яті, ніж той, який ви намагаєтеся виділити, щоб зберігати розмір масиву (який згодом використовується під delete[]час
розгортання

24

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


1
У вас є цитування на це? Ми всі знаємо, що int ar[0];це незаконно, чому це все в порядку?
Мотті

4
Цікаво, що C ++ не настільки сильний, що забороняє об'єкти нульового розміру. Подумайте про оптимізацію порожнього базового класу: Тут також може бути розмір нуля розміру порожнього суб'єкта базового класу. На відміну від цього, стандарт C докладає великих зусиль для того, щоб ніколи не було створено об'єктів нульового розміру: Визначаючи malloc (0), він говорить, що ефект полягає у запуску malloc з деяким ненульовим аргументом, наприклад. І в структурі {...; Т п []; }; коли для масиву не виділено місця (FAM), він говорить, що він поводиться так, ніби "n" має один елемент. (В обох випадках використання об'єкта будь-яким способом є UB, як xn [0])
Johannes Schaub - litb

1
Я думаю sizeof (type), очікується, що він ніколи не поверне нуль. Дивіться, наприклад: stackoverflow.com/questions/2632021/can-sizeof-return-0-zero
pqnet

Навіть так звана "порожня оптимізація базового класу" є актуальною лише через наполягання на тому, що всі об'єкти (на відміну від усіх байтів всередині об'єктів) повинні мати унікальні адреси. C ++ могло бути спрощено, якби код, який фактично піклувався про об'єкти, що мають унікальні адреси, вимагав, щоб вони мали ненульовий розмір.
supercat

15

Що говорить про це стандарт? Чи завжди законно "виділяти" порожній блок пам'яті?

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

Якщо ви виділили більше одного з цих об'єктів, ви побачите, що вони мають різні адреси.


"Кожен об'єкт має унікальну ідентичність, тобто унікальну адресу, яка передбачає ненульову довжину" - вірно, але неправильно. Об'єкт має адресу, але вказівник на об'єкт може вказувати на випадкову пам'ять. "Фактичний об'єм пам'яті буде мовчки збільшений, якщо ви попросите нульових байтів" - не впевнений. operator []також десь зберігає розмір масиву (див. isocpp.org/wiki/faq/freestore-mgmt#num-elems-in-new-array ). Отже, якщо реалізація виділяє кількість байтів з даними, вона може просто виділити для кількості байтів і 0 байт для даних, повертаючи вказівник 1-останнього-останнього.
Steed

Ненульова довжина виділеної пам'яті, а не нульова довжина корисної пам'яті. Моя думка полягала в тому, що два вказівника на два різних об’єкти не повинні вказувати на одну і ту ж адресу.
ChrisW

1
Стандарт ( open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf ) дійсно говорить (3.7.4.1/2), що різні виклики operator new[]повинні повертати різні покажчики. До речі, new expressionмає деякі додаткові правила (5.3.4). Я не міг знайти жодної підказки, що newз розміром 0 насправді потрібно виділити що-небудь. Вибачте, я подав заяву, оскільки я вважаю, що ваша відповідь не відповідає на питання, а надає певні суперечливі твердження.
Steed

@Встановлення пам'яті для зберігання довжини блоку можна неявно обчислити, використовуючи адресний простір. Наприклад, для масивів нульового розміру, можливо, new[]реалізація може повертати адреси в діапазоні, де не відображається динамічна пам'ять, отже, насправді не використовується жодна пам'ять (при використанні адресного простору)
pqnet

@pqnet: Якісна реалізація повинна дозволяти необмежену кількість нових / видальних циклів, чи не так? На платформі з 64-бітовими вказівниками може бути розумним сказати, що комп'ютер, що виконує while(!exitRequested) { char *p = new char[0]; delete [] p; }цикл без переробки покажчиків, розвалиться в пил, перш ніж у нього може не вистачити адресного простору, але на платформі з 32-бітовими вказівниками, який би був набагато менш розумне припущення.
supercat

14

Так, виділяти 0розмірний блок за допомогою цілком законно new. Ви просто не можете з цим зробити нічого корисного, оскільки для вас немає дійсних даних. int[0] = 5;є незаконним.

Однак я вважаю, що стандарт дозволяє такі речі, як malloc(0)повернення NULL.

Вам все одно знадобиться delete []будь-який вказівник, який ви отримаєте від розподілу.


5
Щодо malloc, ви маєте рацію - це визначено реалізацією. Це часто сприймається як невдача.

Я припускаю, що цікаве питання полягає в тому, чи може нерозширена версія нового повернути NULL, якщо вона має розмір 0?
Еван Теран

1
Семантика нового і малок не пов'язана жодним чином, принаймні стандартом.

не кажучи, що вони ... спеціально запитували про нову версію нового.
Еван Теран

@Evan - версія nothrow нового повернення недійсна лише в тому випадку, якщо запит не працює - не тільки якщо розмір запиту дорівнює 0 @Neil - нормативно не пов'язаний - але пов'язаний за наміром (тобто оператор new може бути реалізований з точки зору malloc, але не інший навпаки) - див. виноску, яку я включив у свою відповідь
Faisal Vali

1

Цікаво, що C ++ вимагає, щоб новий оператор повернув законний покажчик, навіть коли запитується нульовий байт. (Вимога цього дивного звучання спрощує речі в інших місцях мови.)

Я виявив, що " Ефективне C ++" третього видання сказано так у "Пункт 51: Дотримуйтесь конвенції під час написання нового та видалення".


0

Я гарантую вам, що новий int [0] коштує вам додаткового місця, оскільки я його протестував.

Наприклад, використання пам'яті

int **arr = new int*[1000000000];

значно менше, ніж

int **arr = new int*[1000000000];
for(int i =0; i < 1000000000; i++) {
    arr[i]=new int[0];
}

Використання пам’яті другого фрагмента коду мінус значення першого фрагмента коду - це пам'ять, яка використовується для численного нового int [0].

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