考虑以下C代码:
int arr[2] = {0, 0};
int *ptr = (int*)&arr;
ptr[0] = 5;
printf("%d\n", arr[0]);
现在,很明显代码在常见编译器上打印5
。但是,有人可以找到C标准中的相关部分,指出代码实际上有效吗?或者是代码未定义的行为?
我基本上要问的是,当&arr
投放到void *
时arr
投放到void *
时与int arr[2] = {0, 0};
int *ptr = (int*)(void*)&arr;
ptr[0] = 5;
printf("%d\n", arr[0]);
相同?因为我相信代码相当于:
{{1}}
我在这里思考这个问题时发明了这个例子:Pointer-to-array overlapping end of array ......但这显然是一个截然不同的问题。
答案 0 :(得分:5)
对于工会和结构,参见ISO 9899:2011§6.7.2.1/ 16f:
16联盟的大小足以容纳其中最大的成员。最多一个成员的值可以随时存储在union对象中。指向适当转换的union对象的指针指向其每个成员(或者如果成员是位字段,则指向它所在的单位),反之亦然。
17在结构对象中,非位字段成员和位域所在的单元具有按声明顺序增加的地址。指向适当转换的结构对象的指针指向其初始成员(或者如果该成员是位字段,则指向它所在的单元),反之亦然。在结构对象中可能有未命名的填充,但不是在它的开头。
对于数组类型,情况稍微复杂一些。首先,观察数组是什么,来自ISO 9899:2011§6.2.5/ 20:
数组类型描述了具有特定成员对象类型的连续分配的非空对象集,称为元素类型。只要指定了数组类型,元素类型就应该是完整的。数组类型的特征在于它们的元素类型和数组中的元素数。数组类型据说是从其元素类型派生的,如果它的元素类型是 T ,则数组类型有时是 称为“ T 的数组”。从元素类型构造数组类型称为“数组类型派生”。
“连续分配”的措辞意味着阵列成员之间没有填充。脚注109确认了这一概念:
两个对象可能在内存中相邻,因为它们是较大数组的相邻元素或结构的相邻成员,它们之间没有填充,或者因为实现选择放置它们,即使它们是不相关的。如果先前的无效指针操作(例如数组边界外的访问)产生了未定义的行为,则后续比较也会产生未定义的行为。
在示例2的§6.5.3.5中使用sizeof
运算符表示在数组之前或之后也没有填充的意图:
因此,我得出结论,指向数组的指针转换为指向该数组元素拼写的指针,指向数组中的第一个元素。此外,观察关于指针的相关定义(§6.5.9/ 6f。):示例2
sizeof运算符的另一个用途是计算元素的数量 在数组中:
sizeof array / sizeof array[0]
6两个指针比较相等,当且仅当两个都是空指针时,两者都是指向同一对象的指针(包括指向对象的指针和开头的子对象)或函数,两者都是指向最后一个元素的指针相同的数组对象,或者一个是指针 到一个数组对象结束的一个,另一个是指向紧跟在地址空间中第一个数组对象之后的另一个数组对象的开头的指针。 109)
7出于这些运算符的目的,指向不是数组元素的对象的指针与指向长度为1的数组的第一个元素的指针的行为相同,其中对象的类型为其元素类型
由于数组的第一个元素是“开头的子对象”,因此指向数组第一个元素的指针和指向数组的指针相等。
答案 1 :(得分:3)
以下是代码的略微重构版本,以便于参考:
int arr[2] = { 0, 0 };
int *p1 = &arr[0];
int *p2 = (int *)&arr;
问题是:p1 == p2
是真的还是未指定的,还是UB?
首先:我认为C&#39抽象记忆模型的作者认为p1 == p2
是真的;如果标准实际上没有拼写出来,那么它将成为标准中的缺陷。
继续前进;唯一相关的文本似乎是C11 6.3.2.3/7(不相关的文本被删除):
指向对象类型的指针可以转换为指向不同对象类型的指针。 [...]再次转换回来时,结果将与原始指针进行比较。
当指向对象的指针转换为指向字符类型的指针时,结果指向对象的最低寻址字节。结果的连续增量,直到对象的大小,产生指向对象剩余字节的指针。
没有具体说明第一次转换的结果是什么。理想情况下它应该说 ...并且指针指向相同的地址,但它没有。
但是,我认为暗示指针必须在转换后指向相同的地址。这是一个说明性的例子:
void *v1 = malloc( sizeof(int) );
int *i1 = (int *)v1;
如果我们不接受" 并且指针指向同一地址"那么i1
可能实际上并没有指向malloc
&#d; d空间,这将是荒谬的。
我的结论是我们应该阅读6.3.2.3/7,因为指针转换不会改变指向的地址。关于使用指向字符类型的指针的部分似乎支持这一点。
因此,由于p1
和p2
具有相同的类型并指向相同的地址,因此它们相等。