Сведения о вопросе

krutoi

18:10, 18th August, 2020

Теги

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

Просмотров: 388   Ответов: 7

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

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



  Сведения об ответе

#hash

23:30, 5th August, 2020

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


  Сведения об ответе

LIZA

08:48, 7th August, 2020

@Derek

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

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

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


  Сведения об ответе

lourence

02:27, 12th August, 2020

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

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


  Сведения об ответе

ASER

12:20, 3rd August, 2020

@James

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

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

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


  Сведения об ответе

repe

05:58, 26th August, 2020

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

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

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

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


  Сведения об ответе

DINO

21:06, 1st October, 2020

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

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

typedef A Arr[NUMELEMENTS];

A* p = new (buffer) Arr;


  Сведения об ответе

PIRLO

18:33, 25th August, 2020

Аналогично тому, как вы использовали бы один элемент для вычисления размера для одного размещения-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();
}


Ответить на вопрос

Чтобы ответить на вопрос вам нужно войти в систему или зарегистрироваться