将`T*`强制转换为`T(*)[N]`是否属于未定义行为?

28

考虑以下情景:

std::array<int, 8> a;
auto p = reinterpret_cast<int(*)[8]>(a.data());
(*p)[0] = 42;

这算是未定义行为吗?我认为是。

  • a.data() 返回一个 int*,它和 int(*)[8] 不同。

  • cppreference 上的类型别名规则似乎表明 reinterpret_cast 是无效的。

  • 作为程序员,我知道 a.data() 指向的内存位置是一个包含 8int 对象的数组。

我错过了什么规则使得这个 reinterpret_cast 是有效的吗?


std::array 要求是一个连续的容器。因此,问题归结为是否可以将已知为有效指针范围 [p; p+N)int * p 强制转换为 int[N] - lisyarus
https://dev59.com/fVYN5IYBdhLWcg3wHlG3 - n. m.
标题有误导性。进行指针转换永远不会产生未定义行为。潜在的问题是通过对转换结果进行解引用来访问内存。 - M.M
2个回答

15
一个数组对象及其第一个元素不能相互转换为指针*,因此 reinterpret_cast 的结果是指向 "包含 8 个 int 的数组" 的指针类型,其值为 "指向 a[0] 的指针"1。换句话说,尽管类型不同,它实际上并不指向任何数组对象。
然后,代码将这样一个指针(作为索引表达式 (*p)[0] 的一部分)的解引用结果应用于数组到指针转换2。当且仅当 lvalue 实际上引用了一个数组对象时,才指定该转换的行为3。由于在这种情况下 lvalue 并没有引用任何数组对象,所以通过省略行为来定义其行为为未定义4

*如果问题是“为什么数组对象及其第一个元素不可相互转换?”,它已经被问过了:指针可转换性与具有相同地址

1请参见 [expr.reinterpret.cast]/7[conv.ptr]/2[expr.static.cast]/13[basic.compound]/4

2请参见 [basic.lval]/6[expr.sub][expr.add]

3[conv.array]:"结果是指向数组第一个元素的指针。"

4[defns.undefined]:未定义行为指"本文档不强制执行任何要求",包括"当本文档省略任何明确的行为定义时"。


1
@hvd 我的意思是指针指向该 int [8] 子对象的子对象;即,它仍然指向数组的一个元素,而不是数组本身。您无法使用 reinterpret_cast 从指向元素的指针获得指向数组的指针。 - T.C.
好的,等我回到电脑前我会检查是否有任何东西可以反驳它,如果没有,我会删除我的答案。同时,你的逻辑表明,std::launder仍然足以使其有效。你同意吗? - user743382
1
只有在满足无额外可达性要求的情况下,才能执行该操作,这将取决于所涉及的数组。 - T.C.
2
@hvd 问题在于您是否可以使用结果指针访问超出该子对象的内容。例如,如果 std::array<int, 8> 类似于 struct {int e[8]; char dummy;};,那么指向该 int[8] 的指针可以与指向整个结构体的指针进行指针互换,因此可以访问 dummy,这使得 launder 不确定。 - T.C.
2
@PasserBy int i; *reinterpret_cast<float*>(&i) = 1; 尝试通过一个类型为 float 的左值引用来修改 i - T.C.
显示剩余5条评论

7

是的,这种行为是未定义的。

int*a.data()的返回类型)与int(*)[8]不同,因此您正在违反严格的别名规则。

当然,这更多地是为了未来的读者受益。

int* p = a.data();

这是完全有效的,随后的表达式p+n也是如此,其中整数类型n的取值在0到8之间(包括0和8)。


5
我正在转换 .data(),而不是数组本身。 - Vittorio Romeo
@VittorioRomeo:哎呀,已经改正了,但答案仍然正确。 - Bathsheba
在我看到的99.9999%可能有用的情况下,我建议使用gsl::span来解决这个问题。 - Mgetz
2
我认为这个答案仍然是误导性的或者有错别字:"int*" 是一个完全不同的类型,与 "std::array<int, N>*" 不同。我没有将其转换为 "std::array<int, N>*",而是转换为 "int(*)[8]"。 - Vittorio Romeo
@VittorioRomeo:再编辑一次——我倾向于这样做。 - Bathsheba
1
我认为使用类型为“int”的lvalue访问类型为“int”的对象(恰好是数组的成员)不违反严格别名规则。 - M.M

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