c++ - 在指向 T 的指针、T 的数组和指向 T 的数组的指针之间进行转换是否有未定义的行为?

标签 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;
}

以上代码在 C 中编译和运行,产生了预期的结果。所有指针值都相同,所有 int 值也是如此。我认为对于任何类型 T,结果都是相同的,但是 int 是最容易使用的。

我承认最初对取消引用数组指针会产生相同的指针值感到惊讶,但经过深思熟虑,我认为这只是我们所熟知和喜爱的数组指针衰减的逆过程。

[编辑:注释掉的行在 C++ 中触发错误,在 C 中触发警告。我发现 C 标准在这一点上含糊不清,但这不是真正的问题。]

this问题,它声称是Undefined Behaviour,但我看不到它。我说得对吗?

代码 here如果你想看的话。


在我写完上面的内容后,我突然意识到这些错误是因为 C++ 中只有一级指针衰减。需要更多取消引用!

 T *p2 = **pa2; //no error in c or c++
 T *p3 = ***pa3; //no error in c or c++

在我设法完成此编辑之前,@AntonSavin 提供了相同的答案。我已编辑代码以反射(reflect)这些更改。

最佳答案

这是一个只有 C 的答案。

C11 (n1570) 6.3.2.3 p7

A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned*) for the referenced type, the behavior is undefined. Otherwise, when converted back again, the result shall compare equal to the original pointer.

*) In general, the concept “correctly aligned” is transitive: if a pointer to type A is correctly aligned for a pointer to type B, which in turn is correctly aligned for a pointer to type C, then a pointer to type A is correctly aligned for a pointer to type C.

标准有点模糊,如果我们将这样的指针(除了严格的别名)用于任何其他用途而不是将其转换回来会发生什么,但是意图和广泛传播的解释是这样的指针应该比较相等(并且具有相同的数值,例如,当转换为 uintptr_t 时它们也应该相等),例如,考虑 (void *)array == (void *)&array (转换为char * 而不是 void * 明确保证可以工作。

T(*pa1)[6] = (T(*)[6])a;

很好,指针正确对齐(它与 &a 的指针相同)。

T(*pa2)[3][2] = (T(*)[3][2])a; // (i)
T(*pa3)[1][2][3] = (T(*)[1][2][3])a; // (ii)

如果 T[6]T[3][2] 具有相同的对齐要求,并且与 T[1][2] 相同[3](i)(ii) 分别是安全的。对我来说,这听起来很奇怪,他们不能,但我无法在标准中找到他们应该具有相同对齐要求的保证。

T *p = a; // safe, of course
T *p1 = *pa1; // *pa1 has type T[6], after lvalue conversion it's T*, OK
T *p2 = **pa2; // **pa2 has type T[2], or T* after conversion, OK
T *p3 = ***pa3; // ***pa3, has type T[3], T* after conversion, OK

忽略传递 int *printf 期望 void * 导致的 UB,让我们看看下一个参数中的表达式 printf,首先是定义好的:

a[5] // OK, of course
(*pa1)[5]
(*pa2)[2][1]
(*pa3)[0][1][2]
p[5] // same as a[5]
p1[5]

请注意,严格的别名在这里不是问题,不涉及输入错误的 lvalue,我们访问 TT .

以下表达式取决于越界指针算法的解释,更宽松的解释(允许 container_ofarray flattening ,带有 char[] 的“struct hack”,等)也允许他们;更严格的解释(允许可靠的运行时边界检查实现指针算法和取消引用,但不允许 container_of,数组展平(但不一定是数组“提升”,你做了什么),struct hack等)呈现它们未定义:

p2[5] // UB, p2 points to the first element of a T[2] array
p3[5] // UB, p3 points to the first element of a T[3] array

关于c++ - 在指向 T 的指针、T 的数组和指向 T 的数组的指针之间进行转换是否有未定义的行为?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25539269/

相关文章:

c++ - Visual C++ 6.0 中的内联函数

c++ - 导航到 vim 中当前 html 标记的末尾?

c++ - 可变参数模板仅在前向声明时编译

c++ - Qt - 在 QAction 中存储小部件指针?

c - 为什么当电流已经超过 `strtod` 时 `DBL_MAX*0.1` 忽略数字

c - 为什么 printf 不能正确显示我的数组?

c - C 中的 MPI INT 数组

c++ - 多个默认构造函数

一个对象可以有多个有效类型吗?

C++ 程序意外退出,如何使用 gdb 进行调试?