Нужно ли использовать std :: launder при выполнении арифметики с указателями в объекте стандартной компоновки (например, с помощью offsetof)? - PullRequest
26 голосов
/ 08 апреля 2019

Этот вопрос является продолжением: Добавляет ли UB указатель "char *", когда он на самом деле не указывает на массив символов?

В CWG 1314 , CWG подтвердила, что допустимо выполнять арифметику указателей в объекте стандартной компоновки с использованием указателя unsigned char.Это может означать, что некоторый код, подобный коду в связанном вопросе, должен работать как задумано:

struct Foo {
    float x, y, z;
};

Foo f;
unsigned char *p = reinterpret_cast<unsigned char*>(&f) + offsetof(Foo, z); // (*)
*reinterpret_cast<float*>(p) = 42.0f;

(я заменил char на unsigned char для большей ясности.)

Однако, похоже, что новые изменения в C ++ 17 подразумевают, что этот код теперь UB, если только std::launder не используется после обоих reinterpret_cast s.Результат reinterpret_cast между двумя типами указателей эквивалентен двум static_cast с: первый - cv void*, второй - типу указателя назначения.Но [expr.static.cast] / 13 подразумевает, что это создает указатель на исходный объект, а не на объект типа назначения, так как объект типа Foo не может быть преобразован в указатель с объектом unsigned char вего первый байт, а также объект unsigned char в первом байте f.z, взаимозаменяемый указателем с самим f.z.

Мне трудно поверить, что комитет намеревался внести изменения, которые бы это сломалиочень распространенная идиома, делающая все пре-C ++ 17 использования offsetof неопределенными.

1 Ответ

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

Ваш вопрос был:

Нужно ли использовать std :: launder при выполнении арифметики с указателем в объекте стандартной компоновки (например, с помощью offsetof)?

Нет.

Использование только стандартного типа макета не может привести к ситуации, когда вам когда-либо потребуется использовать std :: launder.

Пример можно немного упростить: просто используйте целочисленный тип для хранения адреса вместо unsigned char*.

Используя вместо него uintptr_t:

struct Foo {
    float x, y, z;
};

static_assert(std::is_standard_layout<Foo>::value);

Foo f;

uintptr_t addr = reinterpret_cast<uintptr_t>(&f) + offsetof(Foo, z);//OK: storing addr as integer type instead
//uintptr_t addr = reinterpret_cast<uintptr_t>(&f.z);//equivalent: ei. guarenteed to yield same address!

*reinterpret_cast<float*>(addr) = 42.0f;

Пример очень простсейчас - преобразование в unsigned char* больше не выполняется, и мы просто получаем адрес и приводим обратно к исходному типу указателя .. (вы также подразумеваете, что он не работает?)

std::launder обычно простонеобходим в подмножестве случаев (например, из-за константного члена), где вы изменяете (или создаете) базовый объект некоторым временем выполнения способом (например, с помощью размещения new).Мнемоника: объект «грязный» и должен быть std::launder 'изд.

...