Доступ к объекту с использованием reinterpret_cast для объекта типа struct {double, int} - PullRequest
2 голосов
/ 04 июня 2019

Доступ к объектам с помощью reinterpret_cast ed указателей и связанных с ними UB подробно обсуждался здесь. После прочтения вопросов и ответов я все еще не уверен в правильном использовании неинициализированной памяти с типами POD.

Предположим, я хочу "подражать"

struct { double d; int i; };

путем ручного выделения памяти для элементов данных и предположения (для простоты), что до i.

не требуется заполнение.

Теперь я делаю это:

// (V1)
auto buff = reinterpret_cast<char*>(std::malloc(sizeof(double) + sizeof(int)));
auto d_ptr = reinterpret_cast<double*>(buff);
auto i_ptr = reinterpret_cast<int*>(buff + sizeof(double));
*d_ptr = 20.19;
*i_ptr = 2019;

Первый вопрос: действителен ли этот код?

Я мог бы использовать размещение new:

// (V2)
auto buff = reinterpret_cast<char*>(std::malloc(sizeof(double) + sizeof(int)));
auto d_ptr = new(buff) double;
auto i_ptr = new(buff + sizeof(double)) int;
*d_ptr = 20.19;
*i_ptr = 2019;

Должен ли я? Размещение new представляется здесь избыточным, потому что инициализация по умолчанию для типов POD по умолчанию не используется (пустая инициализация), а [basic.life] читает:

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

(1.1) получается хранилище с правильным выравниванием и размером для типа T,

(1.2) если объект имеет не пустую инициализацию, его инициализация завершена, ...

Значит ли это, что время жизни объектов *d_ptr и *i_ptr началось после того, как я выделил для них память?

Второй вопрос: могу ли я использовать double* (или немного T*) для buff, т.е.

// (V3)
auto buff = reinterpret_cast<double*>(std::malloc(sizeof(double) + sizeof(int)));
auto d_ptr = reinterpret_cast<double*>(buff);
auto i_ptr = reinterpret_cast<int*>(buff + 1);
*d_ptr = 20.19;
*i_ptr = 2019;

или

// (V4)
auto buff = reinterpret_cast<double*>(std::malloc(sizeof(double) + sizeof(int)));
auto d_ptr = new(buff) double;
auto i_ptr = new(buff + 1) int;
*d_ptr = 20.19;
*i_ptr = 2019;

Ответы [ 2 ]

5 голосов
/ 05 июня 2019

Как Барри утверждает, что лучше здесь , 1 и 3 - UB. Краткая версия: ни один из этих фрагментов кода не содержит ни одного из синтаксиса, необходимого для создания объекта . И вы не можете получить доступ к значению объекта, которого там нет.

Итак, 2 и 4 работают?

# 2 работает тогда и только тогда, когда alignof(double) >= alignof(int). Но он работает только в том смысле, что создает double, за которым следует int. Он не никоим образом "эмулирует" эту безымянную структуру. Структура может иметь произвольное количество отступов, тогда как в этом случае int будет следовать сразу за double.

# 4 не работает, строго говоря. buff на самом деле не указывает на недавно созданный double. Таким образом, арифметика указателя не может использоваться для получения байта после этого объекта . Таким образом, выполнение арифметики с указателями приводит к неопределенному поведению.

Теперь мы говорим о C ++ , строго говоря . По всей вероятности, каждый компилятор выполнит все четыре из них (с указанным выше предупреждением о выравнивании).

1 голос
/ 04 июня 2019

Когда я смотрю на общедоступный черновик, http://eel.is/c++draft/basic.life цитата отличается, и там говорится, что

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

(1.1) получено хранилище с правильным выравниванием и размером для типа T, и

(1.2) его инициализация (если есть) завершена (включая пустую инициализацию) ([dcl.init]),

Поскольку не было vacuous инициализации двойной переменной, я считаю, что код некорректен и вызывает неопределенное поведение.

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