Допустим, у вас есть объект типа T
и правильно выровненный буфер памяти alignas(T) unsigned char[sizeof(T)]
. Если вы используете std::memcpy
для копирования из объекта типа T
в массив unsigned char
, считается ли это построением копии или присваиванием копии?
Если тип легко копируется, но не является стандартным, вполне возможно, что такой класс:
struct Meow
{
int x;
protected: // different access-specifier means not standard-layout
int y;
};
может быть реализован так, потому что компилятор не вынужден использовать стандартную компоновку:
struct Meow_internal
{
private:
ptrdiff_t x_offset;
ptrdiff_t y_offset;
unsigned char buffer[sizeof(int) * 2 + ANY_CONSTANT];
};
Компилятор может хранить x
и y
Meow в буфере в любой части buffer
, возможно, даже со случайным смещением в пределах buffer
, если они правильно выровнены и не перекрываются. Смещение x
и y
может даже произвольно изменяться для каждой конструкции, если компилятор пожелает. (x
может идти после y
, если компилятор желает, потому что стандарт требует, чтобы члены одного и того же спецификатора доступа шли по порядку, а x
и y
имеют разные спецификаторы доступа.)
Это отвечало бы требованиям возможности тривиального копирования; a memcpy
скопирует скрытые поля смещения, так что новая копия будет работать. Но некоторые вещи не работали. Например, удерживание указателя на x
через memcpy
приведет к поломке:
Meow a;
a.x = 2;
a.y = 4;
int *px = &a.x;
Meow b;
b.x = 3;
b.y = 9;
std::memcpy(&a, &b, sizeof(a));
++*px; // kaboom
Однако действительно ли компилятору разрешено реализовать таким образом тривиально копируемый класс? Разыменование px
должно быть неопределенным, только если время жизни a.x
закончилось. Есть это? Соответствующие части проекта стандарта N3797 не очень ясны по этому вопросу. Это раздел [basic.life] / 1:
время жизни объекта - это свойство объекта во время выполнения. Говорят, что объект имеет нетривиальную инициализацию, если он относится к классу или агрегатному типу и он или один из его членов инициализируется конструктором, отличным от тривиального конструктора по умолчанию. [Примечание: инициализация тривиальным конструктором копирования / перемещения является нетривиальной инициализацией. - конец примечания] Время жизни объекта типа
T
начинается, когда:
- получается хранилище с правильным выравниванием и размером для типа
T
, и- если у объекта нетривиальная инициализация, его инициализация завершена.
Время жизни объекта типа
T
заканчивается, когда:
- если
T
- это тип класса с нетривиальным деструктором ([class.dtor]), запускается вызов деструктора, или- хранилище, которое занимает объект, повторно используется или освобождается.
А это [basic.types] / 3:
Для любого объекта (кроме подобъекта базового класса) тривиально копируемого типа
T
, независимо от того, содержит ли объект допустимое значение типаT
, лежащие в основе байты ([intro.memory]) составляют объект можно скопировать в массивchar
илиunsigned char
. Если содержимое массиваchar
илиunsigned char
копируется обратно в объект, объект впоследствии должен сохранить свое исходное значение. пример пропущен
Тогда возникает вопрос, является ли перезапись тривиально копируемого экземпляра класса «копирующей конструкцией» или «копированием-присваиванием»? Ответ на вопрос, кажется, решает, является ли Meow_internal
допустимым способом для компилятора реализовать тривиально копируемый класс Meow
.
Если memcpy
- это «копирующая конструкция», то ответ таков, что Meow_internal
допустимо, потому что копирующая конструкция повторно использует память. Если memcpy
- «копирование-присваивание», то ответ таков, что Meow_internal
не является допустимой реализацией, потому что присвоение не делает недействительными указатели на созданные экземпляры членов класса. Если memcpy
и то и другое, я понятия не имею, каков ответ.
memcpy
, то это не конструкция или назначение. - person M.M   schedule 03.10.2014memcpy
вместо конструктора :) - person M.M   schedule 03.10.2014T
. Кажется, это лучше подходит для примера кода, чем цитата о символьных массивах IMHO. - person dyp   schedule 03.10.2014memcpy
что-то, что неT
, вT
- что определенно считается повторным использованием хранилища и завершает время жизни объектаT
- я не вижу причин, по которымmemcpy
включениеT
вT
также не считается повторным использованием. И я согласен с @brianbeuning, что обсуждать стандартное соответствие гипотетического компилятора, который ни один здравомыслящий человек никогда бы не написал или не использовал, бессмысленно. - person T.C.   schedule 03.10.2014Meow_internal
нарушает [basic.life] / 7, если ваш компилятор не изменяет указательpx
, если мы заменимmemcpy
наnew((void*)&a) Meow(b);
. (Хотя это может быть тонко:px
указывает на неполный объект; нужно было сделать вывод из других источников, что впоследствии он должен указывать на объект того же типа и т. Д. Но я думаю, что это намерение i> Стандарта.) - person dyp   schedule 03.10.2014Meow_internal
является незаконной реализацией, это означает, что нет технической основы для ограничения Стандарта, чтоoffsetof
требует структуры стандартного макета. Можно было бы формально доказать, что возможность простого копирования будет достаточно для поддержкиoffsetof
, и оправдать изменение Стандартом его определений в результате. - person Myria   schedule 03.10.2014px
не указывает на объект типаT
; он указывает на подобъект, и, насколько я могу судить, нет никакой гарантии, что при повторном использовании хранилища указатели объекта на его подобъекты останутся действительными (конечно, он также повторно использует хранилище*px
, но нет гарантии, что это повторное использование также удовлетворяет другим требованиям [basic.life] / 7). - person T.C.   schedule 03.10.2014reinterpret_cast<std::uintptr_t>(px)
, выполнить XOR полученного беззнакового целого числа со случайным числом, полученным от/dev/urandom
, установитьpx
вnullptr
, а затем выполнитьmemcpy
. После завершенияmemcpy
используйтеreinterpret_cast<int *>(encrypted_uintptr)
для восстановления исходного значения указателя (допустимо [expr.reinterpret.cast] / 5). Компилятор не может узнать, что вы скрыли указатель. (Однако это не будет указатель безопасно извлеченный с помощью [basic.stc.dynamic.safety] / 3). - person Myria   schedule 03.10.2014char
илиunsigned char
, и вы сохраняете указатель на него, ясно, что some элемент массива резервного хранилища будет сравнивать равный этому указателю. Так что говорить, что указатели недействительны, неверно. Возможно, тогда будет правильнее сказать, что их можно использовать ограниченным образом, как в [basic.life] / 5? - person Myria   schedule 03.10.2014offsetof`` would interact with this. It is supposed to work with standard-layout classes, so either
offsetof` нереализуем или ваш гипотетический компилятор нарушает что-то еще. Обратите внимание, чтоoffsetof
- это макрос, который оценивает смещение члена в байтах с учетом имени класса (не экземпляра), подразумевая, что гипотетический компилятор OP не может полностью реализовать стандарт, потому чтоoffsetof
было бы невозможно . cplusplus.com/reference/cstddef/offsetof - person Ben   schedule 30.04.2015Meow
не является стандартным макетом, поэтомуoffsetof
не требуется для работы с ним. Однако цель приведенного выше упражнения - указать на то, что мне кажется глупым в Стандарте. Идея состоит в том, чтобы показать, что совместимая реализация компилятора, в которой тривиально копируемый (т. Е.memcpy
-совместимый) класс не обязательноoffsetof
-совместим, либо противоречит, либо настолько абсурдна, что никогда не будет реализована. Таким образом, было бы оправдано изменить Стандарт, указав, чтоoffsetof
разрешено для тривиально копируемых типов, а не только для типов стандартного макета. - person Myria   schedule 30.04.2015offsetof
, похоже, подразумевает, что смещение члена должно быть одинаковым от экземпляра к экземпляру, что нарушает ваш пример и делает ваш гипотетический компилятор неявно несовместимым, я думаю. Вы бы согласились? - person Ben   schedule 01.05.2015memcpy
осмысленно, является следствием неработающей абстракции, когда время жизни тривиальных объектов начинается и заканчивается отдельно от их хранилища. - person supercat   schedule 19.09.2020foo
содержит элементы структурыs1
иs2
с общей начальной последовательностью, такая модель проясняет, что будет доступно, если код прочитает lvaluefoo.s2.commonMember
после записиfoo.s1.commonMember
. Записьfoo.s1.commonMember
может сделатьfoo.s2
недоступным, но разрешение lvaluefoo.s2
сделает доступным этот уже существующий член, не завершая время жизниfoo.s1
и не делая его недоступным. - person supercat   schedule 19.09.2020