在标准布局对象内进行指针算术运算时(例如使用offsetof),我们是否需要使用std::launder?

29
这个问题是对在不实际指向字符数组的情况下,给“char *”指针添加内容是否未定义行为?的后续提问。
CWG 1314中,CWG确认在标准布局对象内使用无符号字符指针进行指针运算是合法的。这似乎意味着与链接问题中类似的一些代码应该按预期工作。
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中的新变化似乎意味着,除非在两个reinterpret_cast之后使用std::launder,否则此代码现在是未定义行为。在两个指针类型之间进行reinterpret_cast的结果等同于两个static_cast:第一个转换为cv void*,第二个转换为目标指针类型。但是[expr.static.cast]/13暗示这会产生指向原始对象的指针,而不是指向目标类型对象的指针,因为Foo类型的对象与其第一个字节处的unsigned char对象不可互相转换,f.z的第一个字节处的unsigned char对象也不可与f.z本身互相转换。
我很难相信委员会有意打破这个非常常见的习惯用法,使得所有C++17之前使用offsetof的用法都变得未定义。

1
@Artyer 这里不涉及严格别名问题。严格别名规则在C++17之前就已经存在,而std::launder与此无关。 - Brian Bi
2
@Brian:"这会产生指向原始对象的指针" 这里所说的 "原始对象" 是什么? p 不再指向 Foo。 它现在指向一个字节。 当您对字节数组指针执行指针算术运算时,它停止指向 Foo - Nicol Bolas
1
@Brian:但我们谈论的不是 reinterpret cast 的结果;我们谈论的是 byte 指针进行指针算术运算的结果。 - Nicol Bolas
2
@Brian:但是你引用的CWG1314说得不一样。C++17中没有改变这两个方面,因此关于算术运算符你所担心的问题并不清楚。 - Nicol Bolas
2
https://wg21.link/CWG1701。CWG最近似乎没有讨论过这个问题,但是几年前的最新讨论似乎没有明确的方向。 - T.C.
显示剩余14条评论
1个回答

2

你的问题是:

在标准布局对象内进行指针算术运算时(例如使用offsetof),我们需要使用std::launder吗?

不需要。

std::launder在这种情况下不会改变任何内容,因此与所呈现的示例无关(imo从问题中删除launder或提出另一个问题)。

std::launder通常只在一些情况下需要(例如由于const成员而导致),在这些情况下您会以某种运行时方式更改(或创建)基础对象。助记符:对象是“脏的”,需要使用std::launder

仅使用标准布局类型不会导致您需要使用std::launder。


2
size_t 应该表示对象的大小,但可能不足以表示指针。您应该使用 uintptr_tintptr_t - KamilCuk
从整数到指针类型的映射是由实现定义的,因此您的代码是否按预期运行也是由实现定义的。无论如何,我不明白答案与问题有什么关系。问题中没有对整数类型进行强制转换。 - walnut
@walnut 这有点类似于反证法,但我想你是对的。我更新了答案。 - darune

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接