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

时间:2015-03-24 21:20:55

标签: c++ c arrays language-lawyer

这段代码是否正确?

int arr[2];

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

ptr[0][0] = 0;

显然ptr[0][1]访问超出arr的界限将无效。

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

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

6 个答案:

答案 0 :(得分:4)

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

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

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

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

此外,鉴于关于连续的要点以及指向数组中元素的指针必须如何表现,当然必须合法地将数组[x]的任何连续子集分组为数组[y],其中y <1。 x及其上限不超过数组[x]的范围。

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

修改

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

因此,如果没有任何支持来支持我的拙见,那么阵列可能无效或者未定义&#39;作为一个整体,如果它没有真正被统一对待。

统一对待的是单个元素。所以我认为只讨论访问特定元素是有效还是定义是有意义的。

答案 1 :(得分:3)

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

对于C(C11草案N1570)6.5.2.1/3说明相同。

答案 2 :(得分:3)

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

  

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

     

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

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

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

答案 3 :(得分:3)

让我提出一个反对意见:这是(至少在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])。

我认为行为未定义的点在*(ptr+0)的评估中,它与ptr+0相当(相当于5.2.1 [expr.sub]);更准确地说,ptr的评估具有未定义的行为。

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

  

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

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

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

这表明如果代替(*ptr)[0]一个人写**ptr或{{1}},那么行为就不会被定义。这很奇怪,但这不是C ++标准第一次让我感到惊讶。

答案 4 :(得分:2)

这取决于你的意思&#34;正确&#34;。你正在ptr上对arr[1]进行强制转换。在C ++中,这可能是reinterpret_cast。 C和C ++是(大多数时候)假设程序员知道他在做什么的语言。这段代码是错误的,与它是有效的C / C ++代码无关。

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

答案 5 :(得分:0)

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

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(*)[], not arr表示与&arr[0]相同且类型为int*的)是指向数组中第二个字节的指针。然后取消引用ptr与使用ptr算术从char*减去1相同。对于结构和联合,保证指向这些类型的指针与指向这些类型的第一个元素的指针相同,但是在casting pointer to array into pointer中没有找到对数组的这种保证(即,指向数组的指针将是与指向数组第一个元素的指针相同)事实上@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]);