使用单个指针访问2D数组

时间:2014-08-14 08:37:38

标签: c arrays pointers

有很多像这样的代码:

#include <stdio.h>

int main(void)
{
    int a[2][2] = {{0, 1}, {2, -1}};
    int *p = &a[0][0];

    while (*p != -1) {
        printf("%d\n", *p);
        p++;
    }
    return 0;
}

但基于此answer,行为未定义。

N1570。 6.5.6 p8:

  

添加或减去具有整数类型的表达式时   从指针开始,结果具有指针操作数的类型。如果   指针操作数指向数组对象的元素和数组   足够大,结果指向一个偏离的元素   原始元素使得下标的差异   结果和原始数组元素等于整数表达式。   换句话说,如果表达式P指向一个的第i个元素   数组对象,表达式(P)+ N(等效地,N +(P))和(P)-N   (其中N具有值n)分别指向第i + n和第i   数组对象的第i个元素,只要它们存在即可。此外,   如果表达式P指向数组对象的最后一个元素,则   表达式(P)+1指向数组对象的最后一个元素,   如果表达式Q指向一个数组的最后一个元素   对象,表达式(Q)-1指向数组的最后一个元素   宾语。如果指针操作数和结果都指向元素   相同的数组对象,或一个超过数组的最后一个元素   对象,评估不得产生溢出;否则,   行为未定义。如果结果指向最后一个元素   对于数组对象,它不应该用作一元的操作数   *被评估的运算符。

有人可以详细解释一下吗?

3 个答案:

答案 0 :(得分:9)

指定了基址(指向第一个元素的指针)p的数组的类型为int[2]。这意味着p中的地址只能在*p*(p+1)位置合法地取消引用,或者如果您更喜欢下标符号,p[0]和{ {1}}。此外,p[1]保证合法地评估作为地址,并且可比较到该序列中的其他地址,但被解除引用。这是一个过去的地址。

您发布的代码违反了过去的规则,一旦它传递了归属的阵列中的最后一个元素,就会解除引用p+2。它所归属的数组与另一个相似维度的数组相对应,与所引用的正式定义无关。

那就是说,在练习中它起作用,但正如常说的那样。 观察到的行为不是,也不应该被认为是定义的行为。仅仅因为它的工作原理并不合适。

答案 1 :(得分:4)

指针的对象表示是不透明的,在C中。没有禁止对具有编码的边界信息的指针。这是记住的一种可能性。

更实际的是,实现还能够基于由以下规则声明的假设来实现某些优化:别名。

然后保护程序员免受意外伤害。

在函数体内考虑以下代码:

struct {
    char c;
    int i;
  } foo;

char * cp1 = (char *) &foo;
char * cp2 = &foo.c;

鉴于此, cp1 cp2 将相等,但它们的界限仍然不同。 cp1 可以指向 foo 的任何字节,甚至可以指向&#34;一个过去&#34; foo ,但 cp2 只能指向&#34;一个过去&#34; foo.c ,最多,如果我们希望保持定义的行为。

在此示例中, foo.c foo.i 成员之间可能存在填充。虽然填充的第一个字节与&#34;一个过去&#34; foo.c 成员, cp2 + 2 可能会指向其他填充。实现可以在翻译期间注意到这一点,而不是制作程序,它可以告诉您,您可能正在做一些您认为自己没做过的事情。

相比之下,如果您阅读 cp1 指针的初始值设定项,它会直观地表明它可以访问 foo 结构的任何字节,包括填充。

总之,这可能会在转换(警告或错误)或程序执行期间(通过编码边界信息)产生未定义的行为;标准方面没有区别:行为未定义。

答案 2 :(得分:1)

您可以将指针转换为指向数组指针的指针,以确保正确的数组语义。

此代码确实没有定义,但在今天常用的每个编译器中作为C扩展提供。

然而,正确的方法是将指针转换为指向数组的指针,如下所示:

((int(*)[2])p)[0] [0]

获取第0个元素或说:

((int(*)[2])p)[1] [1]

得到最后一个。

要严格,他认为这是非法的,因为你正在打破严格的别名,指向不同类型的指针可能不会指向同一个地址(变量)。

在这种情况下,您正在创建一个指向int数组的指针和一个指向int的指针并将它们指向相同的值,标准不允许这样做,因为唯一可以别名另一个指针的类型是char *甚至这很少用得恰到好处。