Подтвердить что ты не робот

Выравнивание массива с 0 элементами

С++ позволяет динамически распределять массивы нулевого размера:

int* p = new int[0];
delete[] p;

Я не могу много сделать с таким указателем (поскольку у массива нет элементов), но новое выражение требуется, чтобы вернуть верный (!= nullptr) указатель, который затем мне нужно delete[] снова как если это был фактический массив.

Существуют ли какие-либо требования относительно выравнивания памяти, возвращаемой таким новым выражением? Рассмотрим:

struct alignas(8) Foo {
    int x;
};

Foo* p = new Foo[0];
delete[] p;

Может ли p указывать на 8-выровненный адрес? Более того, если я пишу пользовательский распределитель, мне нужно вернуть указатели на выровненные адреса в таком случае?

4b9b3361

Ответ 1

basic.stc.dynamic.allocation/2 из N3337 (в основном С++ 11):

Функция распределения пытается выделить запрошенную сумму место хранения. Если он успешный, он должен вернуть адрес начала блока хранения, длина которого в байтах должна быть как минимум как запрошенный размер. Нет ограничений на содержание выделенное хранилище при возврате из функции распределения. Приказ, смежность и начальное значение хранилища, выделенное последовательными вызовами к функции распределения не определены. Возвращаемый указатель должен быть соответствующим образом выровнены так, чтобы его можно было преобразовать в указатель любого полный тип объекта с фундаментальным требованием выравнивания (3.11) а затем используется для доступа к объекту или массиву в выделенном хранилище (пока хранилище не будет явно освобождено вызовом соответствующая функция освобождения). Даже если размер пространства запрошен ноль, запрос может завершиться неудачей. Если запрос завершается успешно, Возвращаемое значение должно быть не нулевым значением указателя (4.10) p0 di ff erent из любого ранее возвращенного значения p1, если только это значение p1 не было впоследствии передается оператору delete. Эффект разыменования указатель, возвращаемый как запрос на нулевой размер, не найден.

Фундаментальное выравнивание (basic.align/2):

Фундаментальное выравнивание представлено выравниванием меньше или равный наибольшему выравниванию, поддерживаемому реализацией в все контексты, равные align (std:: max_align_t)

Расширенное выравнивание (basic.align/3):

Расширенное выравнивание представлено выравниванием, большим, чем alignof (станд:: max_align_t).

Реализация - определяет, являются ли какие-либо расширенные выравнивания поддерживается и контексты, в которых они поддерживаются.

Итак, возвращаемый указатель operator new должен иметь фундаментальное выравнивание. Даже если задан нулевой размер. И это определяется реализацией, является ли 8 фундаментальным или расширенным выравниванием. Если это фундаментально, то Foo ОК. Если он расширен, то определяется реализация, что Foo поддерживается с помощью operator new.

Обратите внимание, что для С++ 17 ситуация улучшилась:


basic.stc.dynamic.allocation/2 из С++ 17:

Функция распределения пытается выделить запрошенную сумму место хранения. Если он успешный, он должен вернуть адрес начала блока хранения, длина которого в байтах должна быть как минимум как запрошенный размер. Нет ограничений на содержание выделенное хранилище при возврате из функции распределения. Приказ, смежность и начальное значение хранилища, выделенное последовательными вызовами к функции распределения не определены. Возвращаемый указатель должен быть соответствующим образом выровнен таким образом, чтобы его можно было преобразовать в указатель на любой подходящий полный тип объекта ([new.delete.single]), а затем используется для доступ к объекту или массиву в выделенном хранилище (до хранения явно освобождается вызовом соответствующего освобождения функция). Даже если размер запрашиваемого пространства равен нулю, запрос может потерпеть неудачу. Если запрос завершается успешно, возвращаемое значение должно быть значение ненулевого указателя ([conv.ptr]) p0, отличное от любого предыдущего возвращаемое значение p1, если только это значение p1 не было передано оператор удаляет. Кроме того, для функций распределения библиотеки в [new.delete.single] и [new.delete.array], p0 должны представлять адрес блока хранения, не связанного с хранилищем для любого другого объект, доступный для вызывающего. Эффект опосредования через указатель, возвращаемый как запрос нулевого размера, undefined.

Я сделал акцент на соответствующей части. Это предложение означает, что возвращаемый указатель void *operator new(...) должен иметь подходящее выравнивание. Он не упоминает нулевой размер как особый случай (но, конечно, UB разыскивает возвращаемый указатель).

Итак, ответ обычен, нет специальной обработки нуля:

  • void *operator new(std::size_t) должен возвращать выровненный указатель alignof(std​::​max_­align_­t)
  • void *operator new(std::size_t, std::align_val_t align) должен возвращать выровненный указатель align)

Обратите внимание, что это реализация определена, какая версия будет вызываться для Foo. Это зависит от того, равна ли 8 или меньше alignof(std​::​max_­align_­t). Если это меньше, то вызывается 1-я версия (потому что она не имеет расширенного выравнивания). В противном случае вызывается второй.


ОБНОВЛЕНИЕ: Как отмечает Массимилиано Джанс, эти параграфы относятся к результату operator new, а не к результату нового выражения. Реализация может добавить произвольное смещение к результату operator new[]. И стандарт умалчивает о значении этого смещения x:

новый T [5] приводит к одному из следующих вызовов:

оператор new [] (sizeof (T) * 5 + x)

operator new [] (sizeof (T) * 5 + x, std:: align_val_t (alignof (T)))

Здесь каждый экземпляр x является неотрицательным неопределенным значением представление служебных данных распределения массива; результат новое выражение будет компенсировано этой суммой из возвращаемого значения оператором new []. Эти служебные данные могут применяться во всех массивах новые выражения, в том числе ссылки на библиотечную функцию operator new [] (std:: size_t, void *) и другое распределение размещения функции. Количество накладных расходов может варьироваться от одного вызова нового к другому.

Однако, на мой взгляд, это смещение x не может быть произвольным. Если это не кратна выравниванию, то новое выражение вернет не выровненный указатель (во всех случаях. Не только нулевой, но и ненулевой параметр размера). Это явно не мы хотим.

Итак, я думаю, что это дыра в стандарте. Значение x должно быть ограничено кратным выравниванию (по крайней мере, в случае ненулевого распределения). Но из-за этого упущения кажется, что стандарт не гарантирует, что выражение new[] возвращает выравниваемый указатель вообще (в ненулевом случае).