Можно ли использовать новое размещение для массивов переносимым способом?

StackOverflow https://stackoverflow.com/questions/15254

Вопрос

Возможно ли на самом деле использовать placement new в переносимом коде при использовании его для массивов?

Похоже, что указатель, который вы получаете обратно из 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 большой, последний элемент в массиве записывается в нераспределенную кучу.

Итак, вопрос в том, можете ли вы выяснить, сколько дополнительных накладных расходов требуется вашей реализации для безопасного использования placement new[]?В идеале мне нужна техника, переносимая между различными компиляторами.Обратите внимание, что, по крайней мере, в случае VC, накладные расходы, похоже, различаются для разных классов.Например, если я удалю виртуальный деструктор в примере, адрес, возвращаемый из new[], будет таким же, как адрес, который я передаю.

Это было полезно?

Решение

Лично я бы предпочел не использовать размещение new в массиве, а вместо этого использовать размещение 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 в массиве, и убедиться, что деструкторы вызываются для каждого из объектов.Если бы он не знал, сколько их там, он не смог бы этого сделать.

Другие советы

@Дерек

5.3.4, раздел 12 рассказывает о накладных расходах на распределение массива, и, если я не ошибаюсь, мне кажется, это наводит на мысль, что компилятору также допустимо добавлять его при размещении new:

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

Тем не менее, я думаю, что VC был единственным компилятором, который доставлял мне проблемы с этим, за исключением GCC, Codewarrior и ProDG.Однако мне придется проверить еще раз, чтобы быть уверенным.

Спасибо за ответы.Использование placement new для каждого элемента в массиве было решением, которое я в конечном итоге использовал, когда столкнулся с этим (извините, следовало упомянуть об этом в вопросе).Я просто почувствовал, что, должно быть, мне чего-то не хватало в том, чтобы делать это с placement new [].Как бы то ни было, кажется, что размещение new[] по существу непригодно для использования благодаря стандарту, позволяющему компилятору добавлять дополнительные неуказанные служебные данные к массиву.Я не понимаю, как вы вообще могли бы использовать его безопасно и переносимо.

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

@Джеймс

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

Немного поразмыслив над этим, я соглашаюсь с вами.Нет причин, по которым placement new должен сохранять количество элементов, потому что нет placement delete.Поскольку удаление размещения отсутствует, нет причин для размещения new для сохранения количества элементов.

Я также протестировал это с помощью gcc на моем Mac, используя класс с деструктором.В моей системе новое размещение было нет изменение указателя.Это заставляет меня задаться вопросом, является ли это проблемой VC ++, и может ли это нарушать стандарт (стандарт конкретно не рассматривает это, насколько я могу найти).

Placement new сам по себе является переносимым, но предположения, которые вы делаете о том, что он делает с указанным блоком памяти, не являются переносимыми.Как было сказано ранее, если бы вы были компилятором и вам был предоставлен кусок памяти, откуда бы вы узнали, как выделить массив и правильно уничтожить каждый элемент, если бы все, что у вас было, - это указатель?(Смотрите интерфейс оператора delete[].)

Редактировать:

И на самом деле есть удаление размещения, только оно вызывается только тогда, когда конструктор генерирует исключение при выделении массива с размещением new[].

Действительно ли new[] нужно каким-то образом отслеживать количество элементов, зависит от стандарта, который оставляет это на усмотрение компилятора.К сожалению, в данном случае.

Я думаю, что gcc делает то же самое, что и MSVC, но, конечно, это не делает его "переносимым".

Я думаю, вы можете обойти проблему, когда NUMELEMENTS действительно является константой времени компиляции, вот так:

typedef A Arr[NUMELEMENTS];

A* p = new (buffer) Arr;

При этом следует использовать скалярное размещение new.

Аналогично тому, как вы использовали бы один элемент для вычисления размера для одного размещения-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();
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top