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

241

Простое тестовое приложение:

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

выходы:

0x876c0b8

Похоже, это работает. Что стандарт говорит об этом? Всегда ли законно «выделять» пустой блок памяти?


источник
4
+1 Очень интересный вопрос - хотя я не уверен, насколько это важно в реальном коде.
Zifre
37
@Zifre: я прошу любопытства, но это может иметь значение в реальном мире, например, когда размер выделенных блоков памяти вычисляется каким-то образом, а результат вычисления может быть нулевым, то нет прямой необходимости добавлять исключения не выделять блоки нулевого размера. Потому что они должны быть выделены и удалены без ошибок (если только блок нулевого размера не разыменован). Так что обычно это дает более широкую абстракцию того, что представляет собой блок памяти.
2
@ emg-2: В вашем примере ситуация на самом деле не имеет значения, потому что delete [] совершенно допустимо для указателя NULL :-).
Эван Теран
2
Это только косвенно связано - поэтому я комментирую здесь - но C ++ во многих отношениях гарантирует, что отдельные объекты имеют уникальные адреса ... даже если они явно не требуют хранения. Связанный эксперимент должен был бы проверить размер пустой структуры. Или массив этой структуры.
Дрю Дорманн
2
Чтобы уточнить комментарий Shmoopty: особенно при программировании с помощью шаблонов (например, шаблонов классов политики, таких как std :: allocator), в C ++ распространено иметь объекты нулевого размера. Общий код может потребоваться динамически распределять такие объекты и использовать для них указатели для сравнения идентификаторов объектов. Вот почему оператор new () возвращает уникальные указатели для запросов нулевого размера. Хотя, возможно, менее важно / распространено, те же соображения применимы к распределению массивов и оператору new [] ().
Тревор Робинсон

Ответы:

234

От 5.3.4 / 7

Когда значение выражения в direct-new-таком объявителе равно нулю, функция выделения вызывается для выделения массива без элементов.

От 3.7.3.1/2

Эффект разыменования указателя, возвращаемого как запрос нулевого размера, не определен.

Также

Даже если размер запрошенного [нового] ​​пространства равен нулю, запрос может завершиться неудачно.

Это означает, что вы можете сделать это, но вы не можете юридически (четко определенным образом на всех платформах) разыменовать полученную память - вы можете только передать ее в удаление массива - и вы должны удалить ее.

Вот интересная сноска (то есть не нормативная часть стандарта, но включенная в пояснительных целях), приложенная к предложению из 3.7.3.1/2

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

Фейсал Вали
источник
Я получаю утечку памяти, если я не удаляю. Это ожидается? По крайней мере, я не ожидал.
EralpB
12
@EralpB: напротив, даже если ваш запрос равен нулю, это распределение происходит в куче, где один запрос подразумевает несколько операций бухгалтерского учета, таких как выделение и инициализация защит кучи до и после зоны, заданной распределителем, вставка в списки фрилансеров или другие сложные ужасные сооружения. Освободить это значит вести отсталую бухгалтерию.
v.oddou
3
@EralpB да, я думаю, вы можете ожидать утечку памяти каждый раз, когда вы не уравновешиваете new[]с delete[]- независимо от размера. В частности, когда вы вызываете, new[i]вам нужно немного больше памяти, чем той, которую вы пытаетесь выделить, чтобы сохранить размер массива (который позже используется delete[]при освобождении)
pqnet
24

Да, это законно, чтобы выделить массив нулевого размера, как это. Но вы также должны удалить его.


источник
1
У вас есть цитата для этого? Мы все знаем, что int ar[0];это незаконно, почему новое хорошо?
Мотти
4
Интересно, что C ++ не так силен с запретом объектов нулевого размера. Подумайте об оптимизации пустого базового класса: и здесь пустой подобъект базового класса может иметь нулевой размер. В отличие от этого, стандарт C прилагает значительные усилия, чтобы гарантировать, что никогда не будут созданы объекты нулевого размера: при определении malloc (0) он говорит, что эффект запуска malloc с некоторым ненулевым аргументом, например. И в структуре {...; Т н []; }; когда для массива не выделено пространство (FAM), он говорит, что ведет себя так, как будто «n» имеет один элемент. (В обоих случаях использование объекта любым способом - это UB, например xn [0])
Йоханнес Шауб - litb
1
Я думаю, что sizeof (type)ожидается, что никогда не вернет ноль. См. Например: stackoverflow.com/questions/2632021/can-sizeof-return-0-zero
pqnet
Даже так называемая «пустая оптимизация базового класса» имеет значение только потому, что все объекты (в отличие от всех байтов в объектах) должны иметь уникальные адреса. C ++ можно было бы упростить, если бы для обеспечения того, чтобы они имели ненулевой размер, требовался код, который действительно заботился об объектах, имеющих уникальные адреса.
суперкат
15

Что стандарт говорит об этом? Всегда ли законно «выделять» пустой блок памяти?

Каждый объект имеет уникальную идентичность, то есть уникальный адрес, который подразумевает ненулевую длину (фактический объем памяти будет незаметно увеличен, если вы запросите нулевые байты).

Если вы выделите более одного из этих объектов, вы обнаружите, что они имеют разные адреса.

ChrisW
источник
«Каждый объект имеет уникальную идентичность, то есть уникальный адрес, который подразумевает ненулевую длину» - верно, но неверно. Объект имеет адрес, но указатель на объект может указывать на случайную память. «Фактический объем памяти будет молча увеличен, если вы попросите ноль байтов», - не уверен. operator []также где-то хранит размер массива (см. isocpp.org/wiki/faq/freestore-mgmt#num-elems-in-new-array ). Таким образом, если реализация выделяет счетчик байтов для данных, она может просто выделить счетчик байтов и 0 байтов для данных, возвращая указатель 1-last-last.
Конь
Ненулевая длина выделенной памяти, а не ненулевая длина используемой памяти. Я хотел сказать, что два указателя на два разных объекта не должны указывать на один и тот же адрес.
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 на самом деле требуется что-то выделить. Извините, я понизил голос, потому что обнаружил, что ваш ответ не отвечает на вопросы, но дает некоторые противоречивые утверждения.
Конь
@ Гарантированная память для хранения длины блока может быть неявно вычислена с использованием адресного пространства. Например, для массивов нулевого размера new[]реализация могла бы возвращать адреса в диапазоне, где нет сопоставленной динамической памяти, следовательно, фактически не используя какую-либо память (при использовании адресного пространства)
pqnet
@pqnet: Реализация хорошего качества должна позволять неограниченное количество циклов нового / удаления, не так ли? На платформе с 64-разрядными указателями может быть разумно сказать, что компьютер, выполняющий while(!exitRequested) { char *p = new char[0]; delete [] p; }цикл без повторного использования указателей, может превратиться в пыль, прежде чем он может исчерпать адресное пространство, но на платформе с 32-разрядными указателями, которая будет гораздо менее разумное предположение.
Суперкат
14

Да, это совершенно законно, чтобы выделить 0размерный блок с new. Вы просто не можете сделать что-нибудь полезное с этим, так как для вас нет действительных данных. int[0] = 5;незаконно

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

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

Эван Теран
источник
5
Что касается malloc, вы правы - это определяется реализацией. Это часто считается ошибкой.
Я предполагаю, что интересный вопрос: может ли nothrow версия new возвращать NULL, если задан размер 0?
Эван Теран
1
Семантика new и malloc никак не связана, по крайней мере, стандартом.
не говоря, что они ... специально спрашивал о новой версии.
Эван Теран
@Evan - nothrow версия new возвращает ноль только в случае сбоя запроса - не только если размер запроса равен 0 @Neil - не нормативно связан - но связан намерением (то есть оператор new может быть реализован в терминах malloc, но не в другом наоборот) - см. сноску, которую я включил в свой ответ
Фейсал Вали
1

Любопытно, что C ++ требует, чтобы оператор new возвращал законный указатель, даже когда запрашивается ноль байтов. (Требование такого странного поведения упрощает вещи в другом месте языка.)

Я обнаружил, что третье издание Effective C ++ говорит так: «Статья 51: придерживайтесь соглашения при написании нового и удалении».

shuaihanhungry
источник
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].

Ши Цзимин
источник