2008-08-18 5 views
30

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

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

+0

Ах проклятия.Я сделал обман вашего вопроса :([Размещение в массиве - новое требует неопределенных накладных расходов в буфере?] (Http://stackoverflow.com/questions/8720425/array-placement-new-requires-unspecified-overhead-in-the -buffer) – 2015-05-07 18:51:24

+0

Хм ... если служебные данные исчезают, когда вы удаляете виртуальный деструктор, это говорит о том, что накладные расходы вероятны либо из класса vtable, либо из VSTudio в реализации RTTI. – 2016-05-25 18:33:02

+0

Или, по крайней мере, часть накладные расходы. Также возможно, что служебные данные используются только в том случае, если класс имеет нетривиальный деструктор. – 2016-05-25 18:50:30

ответ

22

Лично я бы пошел с возможностью не использовать новое место размещения в массиве и вместо этого использовать размещение нового для каждого элемента в массиве индивидуально. Например:

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; 
} 

Независимо от используемого метода, убедитесь, что вы вручную уничтожить каждый из этих элементов в массиве, прежде чем удалить пиксельный буфер, как вы могли бы в конечном итоге с утечками;)

Примечание : Я не скомпилировал это, но я думаю, что он должен работать (я нахожусь на машине, на которой не установлен компилятор C++). Это все еще указывает на точку :) Надеюсь, это поможет в некотором роде!


Edit:

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

+1

VC++ - плохой компилятор. По умолчанию размещение нового массива aka `new (void *) type [n]` выполняет на месте построение объектов `n` `type`. Указанный указатель должен быть правильно выровнен для соответствия `alignof (type)` (обратите внимание: `sizeof (type)` является кратным `alignof (type)` из-за заполнения). Поскольку вы обычно должны переносить длину массива, нет фактического требования хранить его внутри массива, потому что вы собираетесь уничтожить его с помощью цикла for any (без операторов удаления места). – bit2shift 2016-03-11 21:45:03

+1

@ bit2shift В стандарте C++ явно указано, что `new []` накладывает выделенную память, хотя позволяет заполнить 0 байтов. (См. Раздел «expr.new», в частности примеры (14.3) и (14.4) и объяснение под ними.) Таким образом, в этом отношении он фактически соответствует стандарту. \ [Если у вас нет копии окончательной версии стандарта C++ 14, см. Стр. 133 [здесь] (http://www.open-std.org/jtc1/sc22/wg21/docs/papers /2014/n4296.pdf). \] – 2016-05-25 18:44:23

+1

@JustinTime Это дефект, на который никто не смотрит. – bit2shift 2016-05-26 21:41:19

1

Я думаю, что gcc делает то же самое, что и MSVC, но, конечно, t сделать его «портативным».

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

typedef A Arr[NUMELEMENTS];

A* p = new (buffer) Arr;

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

+1

Это не так. `operator new()` vs `operator new []()` зависит от того, существует ли массив тип, в котором не было `[]` в исходном коде. – 2014-06-27 15:08:27

1

Как и в случае использования одного элемента для вычисления размера для одного места размещения нового, используйте массив этих элементов для вычисления размера, необходимого для массива.

Если вам нужен размер для других вычислений, где количество элементов может быть неизвестно, вы можете использовать sizeof (A [1]) и умножить на свой необходимый элемент.

e.g

char *pBuffer = new char[ sizeof(A[NUMELEMENTS]) ]; 
A *pA = (A*)pBuffer; 

for(int i = 0; i < NUMELEMENTS; ++i) 
{ 
    pA[i] = new (pA + i) A(); 
} 
1

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

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

2

@James

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

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

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

4

@Derek

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

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

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

3

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

Edit:

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

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

Смежные вопросы