指针到数组重叠的数组末尾

这段代码是否正确?

int arr[2]; int (*ptr)[2] = (int (*)[2]) &arr[1]; ptr[0][0] = 0; 

显然,通过访问超出范围的arr ptr[0][1]将是无效的。

注意:毫无疑问, ptr[0][0]指定的内存位置与arr[1] ; 问题是我们是否被允许通过ptr访问该内存位置。 下面是一些表达式确定指定相同内存位置但不允许以这种方式访问​​内存位置的示例。

注2:还要考虑**ptr = 0; 。 正如Marc van Leeuwen所指出的, ptr[0]相当于*(ptr + 0) ,但是ptr + 0似乎与指针运算部分相悖。 但是通过使用*ptr代替,可以避免这种情况。

不是一个答案,而是一个评论,如果不是一个文本墙,我似乎无法说得好:

给定数组保证连续存储它们的内容,以便可以使用指针“迭代”它们。 如果我可以指向数组的开头并连续递增该指针,直到我访问了数组的每个元素,那么肯定会声明数组可以作为一系列由它组成的一系列类型进行访问。

当然组合:1)Array [x]将其第一个元素存储在地址’array’2)连续递增指向它的指针足以访问下一个项目3)Array [x-1]遵守相同的规则

那么至少看一下地址’array’应该是合法的,好像它是类型array [x-1]而不是类型array [x]。

此外,鉴于关于连续的点以及指向数组中元素的指针必须表现出来,当然必须合法地将数组[x]的任何连续子集分组为数组[y],其中y

不是语言律师,这只是我喷出一些垃圾。 我对这次讨论的结果非常感兴趣。

编辑:

在进一步考虑原始代码时,在我看来,在许多方面,数组本身就是一个特殊情况。 它们会衰减到一个指针,我相信可以按照我刚才在这篇文章中所说的那样别名。

因此,没有任何支持来支持我的拙见,如果一个数组不能真正统一地作为一个整体对待,那么它就不能真正无效或整体“未定义”。

得到均匀处理的是个别元素。 所以我认为只讨论访问特定元素是有效还是定义是有意义的。

对于C ++(我正在使用草案N4296) [dcl.array]/7特别说如果下标的结果是一个数组,它会立即转换为指针。 也就是说,在ptr[0][0]中,首先将ptr[0]转换为int* ,然后仅对其应用第二个[0] 。 所以这是完全有效的代码。

对于C(C11草案N1570) 6.5.2.1/3陈述相同。

是的,这是正确的代码。 引用N4140 for C ++ 14:

[expr.sub] / 1 表达式E1[E2]*((E1)+(E2))相同(根据定义*((E1)+(E2))

[expr.add] / 5 如果指针操作数和结果都指向同一个数组对象的元素,或者指向数组对象的最后一个元素,则评估不应产生溢出; 否则,行为未定义。

这里没有溢出。 &*(*(ptr)) == &ptr[0][0] == &arr[1]

对于C11(N1570),规则是相同的。 §6.5.2.1和§6.5.6

让我提出一个反对意见:这是(至少在C ++中)未定义的行为,其原因与此问题所关联的其他问题的原因大致相同。

首先让我用一些简化讨论的typedef来澄清这个例子。

 typedef int two_ints[2]; typedef int* int_ptr; typedef two_ints* two_ints_ptr; two_ints arr; two_ints_ptr ptr = (two_ints_ptr) &arr[1]; int_ptr temp = ptr[0]; // the two_ints value ptr[0] gets converted to int_ptr temp[0] = 0; 

所以问题是,尽管没有类型为two_ints的对象,其地址与arr[1]对象一致(在同一意义上, arr的地址与arr[0]的地址重合),因此没有对象ptr[0]可能指向,但是仍然可以将该表达式的值转换为指向对象的类型为int_ptr (此处为名称temp )的值(即整数对象也称为arr[1] )。

我认为行为未定义的点是在ptr[0]的评估中,它等价(按5.2.1 [expr.sub])到*(ptr+0) ; 更确切地说, ptr+0的评估具有未定义的行为。

我会引用我的C ++副本,这不是官方的[N3337],但可能语言没有改变; 令我困惑的是,章节编号与链接问题的已接受答案中提到的章节编号完全不匹配。 无论如何,对我来说这是§5.7[expr.add]

如果指针操作数和结果都指向同一个数组对象的元素,或者指向数组对象的最后一个元素,则评估不应产生溢出; 否则行为未定义。

由于指针操作数ptr具有指向two_ints类型指针,因此引用文本中提到的“数组对象”必须是two_ints对象的数组。 然而,这里只有一个这样的对象,它的唯一元素是arr的虚拟数组,我们应该在这种情况下让人联想起来(根据:“指向非arrays对象的指针与指向数组的第一个元素的指针的行为相同长度一……“),但显然ptr 并没有指出它独特的元素arr 。 所以即使ptrptr+0毫无疑问是相等的值,它们都没有指向任何数组对象的元素(甚至不是虚构的元素),也不是指向这样一个数组对象的末尾,以及引用的短语不符合。 结果是(不是产生溢出,但是)行为是未定义的。

因此,在应用间接运算符*之前,行为已经未定义。 我不会争论后一种评估的未定义行为,即使短语“结果是指向表达点所指向的对象或函数的左值”很难解释为根本没有引用任何对象的表达式。 但是我在解释这个问题时会宽容,因为我认为解除引用数组的指针本身不应该是未定义的行为(例如,如果用于初始化引用)。

这表明如果不是ptr[0][0] ,而是写了(*ptr)[0]**ptr ,那么行为就不会是未定义的。 这很奇怪,但这不是C ++标准第一次让我感到惊讶。

这取决于你所说的“正确”。 你正在对ptr做一个演员到arr[1] 。 在C ++中,这可能是一个reinterpret_cast 。 C和C ++是(大多数时候)假设程序员知道他在做什么的语言。 这段代码错误与它是有效的C / C ++代码无关。

您没有违反标准中的任何规则(据我所知)。

试着回答这里为什么代码适用于常用的编译器:

 int arr[2]; int (*ptr)[2] = (int (*)[2]) &arr[1]; printf("%p\n", (void*)ptr); printf("%p\n", (void*)*ptr); printf("%p\n", (void*)ptr[0]); 

所有行都在常用编译器上打印相同的地址。 因此, ptr是一个对象,其中*ptr表示与常用编译器上的ptr相同的内存位置,因此ptr[0]实际上是指向arr[1]的指针,因此arr[0][0]arr[1] 。 因此,代码为arr[1]赋值。

现在,让我们假设一个反常的实现,其中指向数组的指针(注意:我说的是指向数组的指针,即&arr的类型为int(*)[] ,而不是arr ,这意味着与&arr[0]int*类型是指向数组中第二个字节的指针。 然后解除引用ptr与使用char* arithmetic从ptr减去1相同。 对于结构和联合,保证指向这些类型的指针与指向这些类型的第一个元素的指针相同,但是在将指向数组的指针转换为指针时,没有找到数组的保证(即指向数组的指针将是与指向数组的第一个元素的指针相同)事实上@FUZxxl计划提交有关标准的缺陷报告。 对于这种不正确的实现, *ptrptr[0]&arr[1] 。 在RISC处理器上,事实上会由于数据对齐而导致问题。

一些额外的乐趣:

 int arr[2] = {0, 0}; int *ptr = (int*)&arr; ptr[0] = 5; printf("%d\n", arr[0]); 

该代码应该有效吗? 它打印5。

更有趣:

 int arr[2] = {0, 0}; int (*ptr)[3] = (int(*)[3])&arr; ptr[0][0] = 6; printf("%d\n", arr[0]); 

这有用吗? 它打印6。

这应该显然有效:

 int arr[2] = {0, 0}; int (*ptr)[2] = &arr; ptr[0][0] = 7; printf("%d\n", arr[0]);