"создание" объекта с возможностью копирования с возможностью memcpy
В С++ этот код правильный?
#include <cstdlib>
#include <cstring>
struct T // trivially copyable type
{
int x, y;
};
int main()
{
void *buf = std::malloc( sizeof(T) );
if ( !buf ) return 0;
T a{};
std::memcpy(buf, &a, sizeof a);
T *b = static_cast<T *>(buf);
b->x = b->y;
free(buf);
}
Другими словами, существует *b
объект, чье жизненное время началось? (Если так, когда это началось точно?)
Ответы
Ответ 1
Это неуказано, которое поддерживается N3751: время жизни объекта, низкоуровневое программирование и
memcpy, который говорит, среди прочего:
В настоящее время стандарты С++ не говорят о том, следует ли использовать memcpy для копии байтов объекта-объекта представляют собой концептуальное назначение или строительство объекта. Разница имеет значение для семантики программный анализ и инструменты преобразования, а также оптимизаторы, время отслеживания объекта. В этой статье предполагается, что
-
использование memcpy для копирования байтов двух разных объектов двух разных тривиальных скопируемых таблиц (но в остальном одинакового размера) допускается
-
такое использование распознается как инициализация или, в более общем смысле, как (концептуально) построение объекта.
Признание в качестве построения объекта будет поддерживать двоичный ввод-вывод, в то время как позволяя анализы и оптимизаторы на протяжении всей жизни.
Я не могу найти какие-либо протоколы встреч, на которых обсуждался этот документ, поэтому кажется, что это все еще проблема.
Стандарт проекта С++ 14 в настоящее время говорит в 1.8
[intro.object]:
[...] Объект создается определением (3.1), посредством нового выражения (5.3.4) или реализацией (12.2), когда это необходимо. [...]
который у нас нет с malloc
, а случаи, описанные в стандарте для копирования тривиальных типов с возможностью копирования, относятся только к уже существующим объектам в разделе 3.9
[basic.types]:
Для любого объекта (кроме подобъекта базового класса) тривиально тип копирования T, независимо от того, имеет ли объект допустимое значение типа T, базовые байты (1.7), составляющие объект, могут быть скопированы в массив char или unsigned char.42 Если содержимое массива char или unsigned char копируется обратно в объект, объект должен впоследствии сохраняют свое первоначальное значение [...]
и
Для любого тривиально-скопируемого типа T, если два указателя на T указывают на различные T-объекты obj1 и obj2, где ни obj1, ни obj2 не являются subobject базового класса, если базовые байты (1.7), составляющие obj1, являются скопированные в obj2,43 obj2, впоследствии должны иметь то же значение, что и obj1. [...]
в основном это предложение, так что это не должно удивлять.
dyp указывает на увлекательную дискуссию по этой теме из списка рассылки ub: [ub] Тип punning, чтобы избежать копирования.
Ответ 2
Из быстрый поиск.
"... время жизни начинается, когда правильно выровненное хранилище для объекта распределяется и заканчивается, когда хранилище освобождается или повторно используется другим объектом."
Итак, я бы сказал, по этому определению, срок жизни начинается с выделения и заканчивается бесплатным.
Ответ 3
Правильно ли этот код?
Ну, это обычно "работает", но только для тривиальных типов.
Я знаю, что вы не просили об этом, но давайте использовать пример с нетривиальным типом:
#include <cstdlib>
#include <cstring>
#include <string>
struct T // trivially copyable type
{
std::string x, y;
};
int main()
{
void *buf = std::malloc( sizeof(T) );
if ( !buf ) return 0;
T a{};
a.x = "test";
std::memcpy(buf, &a, sizeof a);
T *b = static_cast<T *>(buf);
b->x = b->y;
free(buf);
}
После построения a
, a.x
присваивается значение. Предположим, что std::string
не оптимизирован для использования локального буфера для небольших строковых значений, а только для указателя данных на внешний блок памяти. memcpy()
копирует внутренние данные a
as-is в buf
. Теперь a.x
и b->x
относятся к одному и тому же адресу памяти для данных string
. Когда b->x
присваивается новое значение, этот блок памяти освобождается, но a.x
все еще ссылается на него. Когда a
затем выходит из области видимости в конце main()
, он пытается снова освободить тот же блок памяти. Undefined.
Если вы хотите быть "правильным", правильным способом создания объекта в существующий блок памяти является использование вместо этого оператора размещение-новый, например:
#include <cstdlib>
#include <cstring>
struct T // does not have to be trivially copyable
{
// any members
};
int main()
{
void *buf = std::malloc( sizeof(T) );
if ( !buf ) return 0;
T *b = new(buf) T; // <- placement-new
// calls the T() constructor, which in turn calls
// all member constructors...
// b is a valid self-contained object,
// use as needed...
b->~T(); // <-- no placement-delete, must call the destructor explicitly
free(buf);
}