dav*_*pfx 6 c c++ language-lawyer
请考虑以下代码.
#include <stdio.h>
int main() {
typedef int T;
T a[] = { 1, 2, 3, 4, 5, 6 };
T(*pa1)[6] = (T(*)[6])a;
T(*pa2)[3][2] = (T(*)[3][2])a;
T(*pa3)[1][2][3] = (T(*)[1][2][3])a;
T *p = a;
T *p1 = *pa1;
//T *p2 = *pa2; //error in c++
//T *p3 = *pa3; //error in c++
T *p2 = **pa2;
T *p3 = ***pa3;
printf("%p %p %p %p %p %p %p\n", a, pa1, pa2, pa3, p, p1, p2, p3);
printf("%d %d %d %d %d %d %d\n", a[5], (*pa1)[5],
(*pa2)[2][1], (*pa3)[0][1][2], p[5], p1[5], p2[5], p3[5]);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
上面的代码在C中编译并运行,产生预期的结果.所有指针值都是相同的,所有int值都是相同的.我认为任何类型T的结果都是相同的,但是int
最容易使用.
我承认最初感到惊讶的是,取消引用指向数组的指针会产生相同的指针值,但在反射时我认为这仅仅是我们所知道和喜欢的数组到指针衰变的反转.
[编辑:注释掉的行会触发C++中的错误和C中的警告.我发现C标准在这一点上含糊不清,但这不是真正的问题.]
在这个问题中,它声称是未定义的行为,但我看不到它.我对吗?
如果你想看到这里的代码.
在我写完上面之后,我突然意识到这些错误是因为C++中只有一级指针衰减.需要更多解除引用!
T *p2 = **pa2; //no error in c or c++
T *p3 = ***pa3; //no error in c or c++
Run Code Online (Sandbox Code Playgroud)
在我完成这个编辑之前,@ AntonSavin提供了相同的答案.我编辑了代码以反映这些变化.
这是一个仅限 C 的答案。
\nC11 (n1570) 6.3.2.3 p7
\n\n\n指向对象类型的指针可以转换为指向不同对象类型的指针。如果生成的指针未针对引用类型正确对齐*),则行为未定义。否则,当再次转换回来时,结果应等于原始指针。
\n*)一般来说,概念 \xe2\x80\x9c正确对齐\xe2\x80\x9d 是传递性的:如果指向 type 的指针
\nA
对于指向 type 的指针正确对齐B
,而后者又对于指向 type 的指针正确对齐C
,则指向 type 的指针A
与指向 type 的指针正确对齐C
。
标准有点模糊,如果我们使用这样的指针(除了严格的别名)而不是将其转换回来,会发生什么,但意图和广泛的解释是这样的指针应该比较相等(并且具有相同的数值,例如,当转换为 ) 时,它们也应该相等,uintptr_t
作为一个例子,考虑一下(void *)array == (void *)&array
(转换为char *
而不是void *
明确保证可以工作)。
T(*pa1)[6] = (T(*)[6])a;\n
Run Code Online (Sandbox Code Playgroud)\n这很好,指针正确对齐(它\xe2\x80\x99s 与 相同的指针&a
)。
T(*pa2)[3][2] = (T(*)[3][2])a; // (i)\nT(*pa3)[1][2][3] = (T(*)[1][2][3])a; // (ii)\n
Run Code Online (Sandbox Code Playgroud)\niffT[6]
具有与 、 相同的对齐要求,并且分别与、和T[3][2]
相同,都是安全的。对我来说,这听起来很奇怪,他们不能 \xe2\x80\x99t,但我无法在标准中找到他们应该具有相同对齐要求的保证。T[1][2][3]
(i)
(ii)
T *p = a; // safe, of course\nT *p1 = *pa1; // *pa1 has type T[6], after lvalue conversion it\'s T*, OK\nT *p2 = **pa2; // **pa2 has type T[2], or T* after conversion, OK\nT *p3 = ***pa3; // ***pa3, has type T[3], T* after conversion, OK\n
Run Code Online (Sandbox Code Playgroud)\nint *
忽略因传递where printf
Expects引起的 UB void *
,让\xe2\x80\x99s 查看下一个参数中的表达式printf
,首先是定义的表达式:
a[5] // OK, of course\n(*pa1)[5]\n(*pa2)[2][1]\n(*pa3)[0][1][2]\np[5] // same as a[5]\np1[5]\n
Run Code Online (Sandbox Code Playgroud)\n请注意,严格的别名在这里不是问题,不涉及错误输入的左值T
,并且我们访问为T
.
以下表达式取决于越界指针算术的解释,更宽松的解释(允许container_of
、数组扁平化、 \xe2\x80\x9cstruct hack\xe2\x80\x9dchar[]
等)也允许它们;更严格的解释(允许对指针算术和解引用进行可靠的运行时边界检查实现,但不允许container_of
数组扁平化(但不一定是数组 \xe2\x80\x9clifting\xe2\x80\x9d,你所做的),结构hack 等)使它们未定义:
p2[5] // UB, p2 points to the first element of a T[2] array\np3[5] // UB, p3 points to the first element of a T[3] array\n
Run Code Online (Sandbox Code Playgroud)\n