обработка памяти, возвращаемой оператором new (sizeof (T) * N), как массива - PullRequest
0 голосов
/ 23 ноября 2018

В C можно выделить динамические массивы, используя malloc (sizeof (T) * N), а затем использовать арифметику указателей для получения элементов со смещением i в этом динамическом массиве.

В C ++ можно сделать аналогичное использование оператораnew () таким же образом, как malloc (), и затем размещение new (например, решение по пункту 13 можно увидеть в книге Херба Саттера «Исключительные C ++: 47 инженерных головоломок, проблем программирования и решений»).Если у вас его нет, резюме решения этого вопроса будет следующим:

T* storage = operator new(sizeof(T)*size);

// insert element    
T* p = storage + i;
new (p) T(element);

// get element
T* element = storage[i];

Для меня это выглядело вполне законно, так как я просил кусок памяти с достаточным объемом памяти, чтобы удерживать N выровненнымэлементы размера = sizeof (T).Поскольку sizeof (T) должен возвращать размер элемента, который выровнен, и они располагаются один за другим в куске памяти, здесь хорошо использовать арифметику указателей.

Однако я тогда указал на такие ссылки, как:http://eel.is/c++draft/expr.add#4 или http://eel.is/c++draft/intro.object#def:object и утверждение, что в операторе C ++ new () не возвращает объект массива, поэтому арифметика указателя на то, что он возвратил, и использование его в качестве массива является неопределенным поведением в отличие от ANSIC.

Я не настолько хорош в таких низкоуровневых вещах, и я действительно пытаюсь понять, читая это: https://www.ibm.com/developerworks/library/pa-dalign/ или это: http://jrruethe.github.io/blog/2015/08/23/placement-new/ но я все еще терплю неудачучтобы понять, что Саттер был просто неправ?

Я понимаю, что функции alignas имеют смысл в таких конструкциях, как:

alignas(double) char array[sizeof(double)];

(c) http://georgeflanagin.com/alignas.php

Если массивпохоже, что он не находится на границе двойного (возможно, следующий символ в структуре работает с 2-байтовым процессором чтения).

Но это не так - я запросил память у специально запрошенного оператора heap / free storageНебраскаw, чтобы вернуть память, в которой элементы будут выровнены по размеру (T).

Подведем итог, если это был TL; DR:

  • Возможно ли использовать malloc () для динамическогомассивы в C ++?
  • Можно ли использовать оператор new () и размещать новые для динамических массивов в более старом C ++, у которого нет ключевого слова alignas?
  • Является ли арифметика с использованием указателя неопределенным поведением при использовании из памяти, возвращаемойоператором new ()?
  • Саттер советует код, который может сломаться на какой-нибудь античной машине?

Извините, если это глупо.

Ответы [ 4 ]

0 голосов
/ 23 ноября 2018

Стандарты C ++ содержат открытую проблему о том, что базовое представление объектов - это не «массив», а «последовательность» из unsigned char объектов.Тем не менее, каждый рассматривает его как массив (который предназначен), поэтому можно написать код, например:

char* storage = static_cast<char*>(operator new(sizeof(T)*size));
// ...
char* p = storage + sizeof(T)*i;  // precondition: 0 <= i < size
new (p) T(element);

, пока void* operator new(size_t) возвращает правильно выровненное значение.Использование sizeof умноженных смещений для сохранения выравнивания: safe .

В C ++ 17 есть макрос STDCPP_DEFAULT_NEW_ALIGNMENT ,который задает максимальное безопасное выравнивание для «нормального» * ​​1017 *, и void* operator new(std::size_t size, std::align_val_t alignment) следует использовать, если требуется большее выравнивание.

В более ранних версиях C ++ такого различия нет, что означает, что void* operator new(size_t) должен быть реализован способом, совместимым с выравниванием любого объекта.

Что касается возможности делать арифметику указателей непосредственно на T*, я не уверен, что нуждается по требованию стандарта.Однако сложно реализовать модель памяти C ++ таким образом, чтобы она не работала.

0 голосов
/ 23 ноября 2018

Вы можете сделать это со «старомодным» malloc, который дает вам блок памяти, который выполняет наиболее ограничивающее выравнивание на соответствующей платформе (например, у long long double).Таким образом, вы сможете поместить любой объект в такой буфер, не нарушая каких-либо требований выравнивания.

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

struct MyType {
    MyType() {
        cout << "in constructor of MyType" << endl;
    }
    ~MyType() {
        cout << "in destructor of MyType" << endl;
    }
    int x;
    int y;
};

int main() {

    char* buffer = (char*)malloc(sizeof(MyType)*3);
    MyType *mt = new (buffer)MyType[3];

    for (int i=0; i<3; i++)  {
        mt[i].~MyType();
    }
    free(mt);
}

Обратите внимание, что - как всегда с размещением нового - вам придется позаботиться о явном вызове деструкторов и освобождении памяти на отдельном этапе;Вы не должны использовать delete или delete[] -функции, которые объединяют эти два шага и тем самым освобождают память, которой они не владеют.

0 голосов
/ 23 ноября 2018

Для всех широко используемых в последнее время posix-совместимых систем, то есть для Windows, Linux (и Android ofc.) И MacOSX, применяются следующие условия:

Возможно ли использовать malloc ()для динамических массивов в C ++?

Да, это так.Лучше всего использовать reinterpret_cast для преобразования результирующего void* в требуемый тип указателя, и это приводит к динамически распределенному массиву, подобному следующему: type *array = reinterpret_cast<type*>(malloc(sizeof(type)*array_size); Будьте осторожны, чтобы в этом случае конструкторы не вызывалисьэлементы массива, поэтому это все еще неинициализированное хранилище, независимо от того, что является type.Деструкторы также не вызываются, когда free используется для освобождения


Возможно ли использовать оператор new () и размещение нового для динамических массивов в более старом C ++, который не имеет ключевого слова alignas?

Да, но вы должны знать о выравнивании в случае размещения нового, если вы вводите его в пользовательских местах (то есть тех, которые не происходят из malloc / new).Обычный оператор new, также как и malloc, предоставит собственные области памяти с выравниванием по словам (по крайней мере, когда размер выделения> = wordsize).Этот факт и тот факт, что структура макетов и размеров определяется так, чтобы выравнивание учитывалось правильно, вам не нужно беспокоиться о выравнивании массивов dyn, если используется malloc или new. Можно заметить, что размер слова иногда значительно меньше, чем самый большой встроенный тип данных (который обычно long double), но он должен быть выровнен таким же образом, поскольку выравнивание - это не размер данных, абитовая ширина адресов на шине памяти для разных размеров доступа.


Является ли указатель арифметическим неопределенным поведением при использовании над памятью, возвращаемым оператором new ()?

Нет, пока вы уважаете границы памяти процесса - с этой точки зрения new в основном работает так же, как malloc, более того, new фактически вызывает malloc в подавляющем большинстве реализаций в порядкеприобрести необходимую площадь.На самом деле, арифметика указателей как таковая никогда не является недействительной.Однако результат арифметического выражения, которое оценивает указатель, может указывать на местоположение за пределами разрешенных областей, но это не ошибка арифметики указателя, а ошибочное выражение.


Саттер советует код, который может сломаться на какой-нибудь античной машине?

Я так не думаю, если используется правильный компилятор.(не компилируйте инструкции avr или 128-битные модули памяти в двоичный файл, предназначенный для работы на 80386). Конечно, на разных машинах с разным объемом памяти и разметкой один и тот же литеральный адрес может получить доступ к областям разного назначения / статуса./ существование, но зачем вам использовать буквенные адреса, если вы не пишете код драйвера для конкретного оборудования? ...:)

0 голосов
/ 23 ноября 2018

Проблема арифметики указателя на выделенной памяти, как в вашем примере:

T* storage = operator new(sizeof(T)*size);
// ...
T* p = storage + i;  // precondition: 0 <= i < size
new (p) T(element);

, являющаяся технически неопределенным поведением, давно известна.Это подразумевает, что std::vector не может быть реализовано с четко определенным поведением исключительно как библиотека, но требует дополнительных гарантий от реализации помимо тех, которые содержатся в стандарте.

Это определенно не было целью стандартовкомитет сделать std::vector невыполнимым.Саттер, конечно, прав, что такой код предназначен для , чтобы быть четко определенным.Формулировка стандарта должна отражать это.

P0593 - это предложение, которое, если оно будет принято в стандарте, может решить эту проблему.В то же время можно продолжать писать код, подобный приведенному выше;ни один крупный компилятор не будет рассматривать его как UB.

Редактировать: Как указано в комментариях, я должен был сказать, что когда я сказал, что storage + i будет четко определено в P0593, япредполагалось, что элементы storage[0], storage[1], ..., storage[i-1] уже построены.Хотя я не уверен, что понимаю P0593 достаточно хорошо, чтобы сделать вывод, что он также не будет охватывать случай, когда эти элементы не были уже построены.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...