Что такое эквивалент С++ "выделенный объект, не имеющий объявленного типа" в C?

Я пишу диспетчер памяти для своей виртуальной машины на С++. Ну, точнее, инструкции VM будут скомпилированы в С++ с встроенным менеджером памяти. Мне гораздо удобнее обрабатывать C, но теперь мне нужна встроенная поддержка обработки исключений, в значительной степени единственная причина, по которой я использую С++.

И C и С++ имеют строгое правило сглаживания, что два объекта несовместимых типов не должны пересекаться, с небольшим исключением в C для объединений. Но для определения поведения функций выделения памяти, таких как malloc, calloc, alloca и т.д., Стандарт C имеет следующий абзац.

6.5-6 Эффективным типом объекта для доступа к его сохраненному значению является объявленный тип объекта, если таковой имеется. Выделенные объекты не имеют объявленного типа. Если значение хранится в объекте, не имеющем объявленный тип через lvalue, имеющий тип, который не является символом тип, то тип lvalue становится эффективным типом объект для этого доступа и для последующих обращений, которые не изменяют сохраненное значение. Если значение копируется в объект, не имеющий объявленного типа, используя memcpy или memmove, или копируется как массив типа символа, тогда эффективный тип измененного объекта для этого доступа и для последующих обращений, которые не изменяются значение - это эффективный тип объекта, из которого копируется значение, если оно есть. Для всех других доступов к объекту, не имеющему объявленного типа, эффективным типом объекта является просто тип lvalue, используемый для доступа.

Это эффективно делает использование необработанной выделенной памяти для любого типа хорошо определенным поведением в C. Я попытался найти аналогичный абзац в стандартном документе С++, но не смог его найти. Я думаю, что у С++ есть другой подход в этом отношении. Что такое эквивалент С++ "выделенный объект, не имеющий объявленного типа" в C, и как его определяет стандарт С++?

Ответ 1

Я думаю, что подход С++ в этом отношении можно суммировать так: "malloc() является злом. Вместо этого используйте new. Нет такого объекта, как объект без объявленного типа". Конечно, реализации С++ должны определять глобальный operator new(), который в основном является версией С++ malloc() (и которая может быть предоставлена ​​пользователем). Само существование этого оператора доказывает, что в С++ есть что-то вроде объектов без объявленного типа, но стандарт не допустит этого.

Если бы я был вами, я бы принял прагматичный подход. И глобальные operator new(), и malloc() доступны на С++, поэтому любая реализация должна иметь возможность использовать свои возвращаемые значения разумно. Особенно malloc() будет вести себя одинаково в C и С++. Таким образом, просто обработайте эти нетипизированные объекты так же, как вы бы обрабатывали их на C, и вы должны быть в порядке.

Ответ 2

Для С++ это описано в Object lifetime [object.life]; в частности:

Время жизни объекта типа T начинается, когда:

  • получено хранилище с правильным выравниванием и размером для типа T и
  • Если объект имеет нетривиальную инициализацию, его инициализация завершена.

Срок службы продолжается до тех пор, пока хранилище не будет повторно использовано или объект не будет уничтожен:

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

Это имеет довольно странное значение, что неиспользуемое выделенное хранилище (возвращается из operator new) содержит объект любого тривиального типа, который помещается в этот блок хранения, по крайней мере, до тех пор, пока не будет использован блок хранения. Но тогда стандарт больше заботится о том, чтобы лечить нетривиальные типы, чем эта незначительная бородавка.

Ответ 3

Я думаю, что "выделенный объект без объявленного типа" - это фактически выделенное хранилище, которое еще не инициализировано и в этом пространстве памяти еще не создан объект. Из глобальной функции распределения ::operator new(std::size_t) и семейства (§ 3.7.4/2);

§3.7.4.1/2

Функция распределения пытается выделить запрошенный объем памяти. Если он успешный, он должен вернуть адрес начала блока хранения, длина которого в байтах должна быть не меньше размера запрашиваемого. Нет ограничений на содержимое выделенного хранилища при возврате из функции выделения.

Создание объекта, будь то автоматическое или динамическое, определяется двумя этапами, самим распределением, а затем конструкцией объекта в этом пространстве.

§3.8/1

Время жизни объекта - это свойство времени выполнения объекта. Говорят, что объект имеет непустую инициализацию, если он имеет тип класса или агрегата, и он или один из его членов инициализируется конструктором, отличным от тривиального конструктора по умолчанию. [Примечание: инициализация тривиальным конструктором copy/move - это непустая инициализация. - end note] Время жизни объекта типа T начинается, когда:

  • получено хранилище с правильным выравниванием и размером для типа T и
  • Если объект имеет нетривиальную инициализацию, его инициализация завершена.

Соответственно,

Время жизни объекта типа T заканчивается, когда:

  • Если T - это тип класса с нетривиальным деструктором (12.4), начинается вызов деструктора или
  • хранилище, которое объект занимает, повторно используется или освобождается.

С++ WD n4527.

Ответ 4

Я не уверен, что такой аналог существует или нужен в С++. Чтобы выделить память, вы можете сделать одну из трех вещей

Foo f;

Это будет выделять sizeof(Foo) количество памяти в стеке. Размер известен во время компиляции, так как компилятор знает, сколько места выделяется. То же самое относится к массивам и т.д.

Другой вариант -

Foo* f = new Foo;  // or the smart pointer alternatives

Это будет выделено из кучи, но опять же sizeof(Foo) должно быть известно, но это позволяет выделить во время выполнения.

Третий вариант, как @BasileStarynkevitch упоминает, размещение нового

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

Хотя в С++ можно использовать malloc и т.д., он нахмурился, поскольку он идет против зерна для типичной семантики С++. Вы можете увидеть обсуждение аналогичного вопроса. Другие механизмы выделения "сырой" памяти являются взломанными. Например

void* p = operator new(size);

Это будет выделять size количество байтов.