Можно ли реально использовать размещение нового в переносимом коде при использовании его для массивов?
Похоже, что указатель, который вы получаете от new [], не всегда совпадает с адресом, который вы передаете (5.3.4, примечание 12 в стандарте, кажется, подтверждает, что это правильно), но я не понимаю, как вы может выделить буфер для массива, чтобы войти, если это так.
В следующем примере показана проблема. Этот пример, скомпилированный с Visual Studio, приводит к повреждению памяти:
#include <new>
#include <stdio.h>
class A
{
public:
A() : data(0) {}
virtual ~A() {}
int data;
};
int main()
{
const int NUMELEMENTS=20;
char *pBuffer = new char[NUMELEMENTS*sizeof(A)];
A *pA = new(pBuffer) A[NUMELEMENTS];
// With VC++, pA will be four bytes higher than pBuffer
printf("Buffer address: %x, Array address: %x\n", pBuffer, pA);
// Debug runtime will assert here due to heap corruption
delete[] pBuffer;
return 0;
}
Глядя на память, кажется, что компилятор использует первые четыре байта буфера для хранения количества элементов в нем. Это означает, что поскольку буфер только sizeof(A)*NUMELEMENTS
большой, последний элемент в массиве записывается в нераспределенную кучу.
Таким образом, вопрос заключается в том, можете ли вы узнать, сколько дополнительных затрат требует ваша реализация для безопасного использования размещения new []? В идеале мне нужна техника, которая переносима между разными компиляторами. Обратите внимание, что, по крайней мере в случае VC, накладные расходы различаются для разных классов. Например, если я удаляю виртуальный деструктор в примере, адрес, возвращаемый из new [], совпадает с адресом, который я передаю.
Лично я бы выбрал вариант не использовать размещение нового в массиве, а вместо этого использовать размещение нового для каждого элемента в массиве по отдельности. Например:
int main(int argc, char* argv[])
{
const int NUMELEMENTS=20;
char *pBuffer = new char[NUMELEMENTS*sizeof(A)];
A *pA = (A*)pBuffer;
for(int i = 0; i < NUMELEMENTS; ++i)
{
pA[i] = new (pA + i) A();
}
printf("Buffer address: %x, Array address: %x\n", pBuffer, pA);
// dont forget to destroy!
for(int i = 0; i < NUMELEMENTS; ++i)
{
pA[i].~A();
}
delete[] pBuffer;
return 0;
}
Независимо от используемого вами метода, убедитесь, что вы вручную уничтожили каждый из этих элементов в массиве, прежде чем удалять pBuffer, так как это может привести к утечкам;)
Примечание : я не скомпилировал это, но я думаю, что это должно работать (я на машине, на которой не установлен компилятор C ++). Это по-прежнему указывает на смысл :) Надеюсь, это поможет каким-то образом!
Редактировать:
Причина, по которой ему необходимо отслеживать количество элементов, заключается в том, что он может выполнять итерацию по ним, когда вы вызываете delete для массива, и убедитесь, что деструкторы вызваны для каждого из объектов. Если он не знает, сколько их, он не сможет этого сделать.
new(void*) type[n]
выполняет на месте построение n
объектов type
. Указанный указатель должен быть правильно выровнен для соответствия alignof(type)
(примечание: sizeof(type)
кратно из- alignof(type)
за заполнения). Поскольку вам обычно приходится иметь дело с длиной массива, нет фактического требования хранить его внутри массива, потому что вы все равно собираетесь уничтожить его с помощью цикла for (без операторов удаления размещения). new[]
выделенную память, хотя он допускает заполнение 0 байтов. (См. Раздел «expr.new», в частности примеры (14.3) и (14.4) и пояснения под ними.) Таким образом, в этом отношении он фактически соответствует стандартам. [Если у вас нет копии окончательной версии стандарта C ++ 14, см. Стр. 133. здесь .]void* operator new[](std::size_t count, void* ptr);
когда этот оператор, как известно, является «безоперационным» (без выделения), и также ясно известно, что указатель, возвращаемый этим оператором или его скалярным родственным элементом, не может быть переданным либо, delete
либо delete[]
, требуя, чтобы программист вручную уничтожил каждый элемент. Я думаю, что gcc делает то же самое, что и MSVC, но, конечно, это не делает его «переносимым».
Я думаю, что вы можете обойти проблему, когда NUMELEMENTS действительно является постоянной времени компиляции, например так:
typedef A Arr[NUMELEMENTS];
A* p = new (buffer) Arr;
Это должно использовать скалярное размещение нового.
operator new()
vs operator new[]()
зависит от того, задействован ли тип массива, а не от того, были ли они []
в исходном коде. Аналогично тому, как вы будете использовать один элемент для расчета размера для одного нового размещения, используйте массив этих элементов для расчета размера, необходимого для массива.
Если вам требуется размер для других вычислений, где число элементов может быть неизвестно, вы можете использовать sizeof (A [1]) и умножить на требуемое количество элементов.
например
char *pBuffer = new char[ sizeof(A[NUMELEMENTS]) ];
A *pA = (A*)pBuffer;
for(int i = 0; i < NUMELEMENTS; ++i)
{
pA[i] = new (pA + i) A();
}
sizeof(A[NUMELEMENTS])
в случае new []
. sizeof(A[N])
просто будет N * sizeof(N)
в общем и не будет отражать это дополнительное необходимое пространство. Спасибо за ответы. Использование размещения нового для каждого элемента в массиве было решением, которое я использовал, когда столкнулся с этим (извините, я должен был упомянуть об этом в вопросе). Я просто чувствовал, что, должно быть, чего-то не хватало в том, чтобы делать это с размещением new []. На самом деле, кажется, что размещение new [] по существу непригодно благодаря стандарту, позволяющему компилятору добавлять дополнительные неопределенные издержки в массив. Я не понимаю, как вы могли бы использовать это безопасно и переносимо.
Я даже не совсем понимаю, зачем ему нужны дополнительные данные, так как вы все равно не будете вызывать delete [] для массива, поэтому я не совсем понимаю, зачем ему нужно знать, сколько в нем элементов.
@Джеймс
Я даже не совсем понимаю, зачем ему нужны дополнительные данные, так как вы все равно не будете вызывать delete [] для массива, поэтому я не совсем понимаю, зачем ему нужно знать, сколько в нем элементов.
Подумав об этом, я согласен с вами. Нет причины, по которой для размещения new нужно хранить количество элементов, потому что нет места размещения, удаляющего. Поскольку нет места размещения, нет никакой причины для размещения нового, чтобы сохранить количество элементов.
Я также проверил это с помощью gcc на моем Mac, используя класс с деструктором. В моей системе размещение нового не меняло указатель. Это заставляет меня задуматься о том, является ли это проблемой VC ++, и может ли это нарушать стандарт (насколько я могу найти, стандарт конкретно не решает эту проблему).
-std=c++14
и -pedantic
на нем . Ни один из них не показал существования накладных расходов, особенно с классами с нетривиальными деструкторами. Итак, я думаю, это еще один пример того, как Visual C ++ такой плохой компилятор. nothrownew.obj
способ заставить new
себя вести себя как new(std::nothrow)
. @Derek
5.3.4, в разделе 12 говорится о накладных расходах на выделение массива, и, если я не читаю его, мне кажется, что для компилятора допустимо добавить его и при размещении нового:
Эти издержки могут применяться во всех новых выражениях массива, включая те, которые ссылаются на оператор библиотечной функции new [] (std :: size_t, void *) и другие функции размещения размещения. Сумма накладных расходов может варьироваться от одного вызова нового к другому.
Тем не менее, я думаю, что VC был единственным компилятором, который доставил мне проблемы с этим, из-за этого, GCC, Codewarrior и ProDG. Я должен был бы проверить еще раз, чтобы быть уверенным, хотя.
new(pointer) type[length]
. VC - плохой компилятор, все должны это знать. Само новое размещение является переносимым, но предположения о том, что он делает с указанным блоком памяти, не переносимы. Как и то, что было сказано ранее, если бы вы были компилятором и получили кусок памяти, как бы вы узнали, как распределить массив и правильно уничтожить каждый элемент, если бы у вас был только указатель? (См. Интерфейс оператора delete [].)
Редактировать:
И на самом деле есть удаление размещения, только оно вызывается, только когда конструктор выдает исключение при выделении массива с размещением new [].
Нужно ли new [] действительно отслеживать количество элементов каким-либо образом - это то, что остается на уровне стандарта, что оставляет его на усмотрение компилятора. К сожалению, в этом случае.
delete[]
потребности выражения , чтобы знать, но он может быть использован только на результате new[]
выражения с кучей распределения (либо бросать или nothrow
). Для размещения new[]
это действительно не нужно. Единственное объяснение, которое я имею для поведения VC, состоит в том, что куча new[]
каким-то образом реализована с точки зрения размещения new[]
и не хранит количество элементов.