【问题标题】:How are 2d Array and pointers related? [duplicate]二维数组和指针有什么关系? [复制]
【发布时间】:2019-02-12 18:10:00
【问题描述】:

为什么这两个是相同的东西我看到了这个类似问题的一个答案但无法真正理解。

为什么*(a+i)a+i 做同样的工作。

int a[1][2] = {1,2};
printf("%p or %p\n",*(a+0),a+0);   

【问题讨论】:

  • *(a+0) 表示a[0]a+0 表示&(a[0])a[0] 是一个数组(a 的第一行),所以这和问为什么数组的地址等于它的值是同一个问题

标签: c arrays pointers multidimensional-array


【解决方案1】:

数组下标括号“[]”是根据指针操作定义的。 a[i] 定义为 *(a+i),假设 a 是数组类型,而 i 是整数类型。数组名本身被评估为指向其第一个元素的指针;指针的整数加法将整数乘以元素的大小添加到指针。 (a+i) 和 *(a+1) 不同 - (a+i) 是数组中元素的地址,*(a+i) 是该元素。

因此 a[0] 变成 *(a+0) 或 *a,数组的第一个元素。 A[1] 变为 *(a+1),或 *(a + (sizeof (*a) * 1),或 *(数组第二个元素的地址),或只是数组的第二个元素,如人们会期待的。

【讨论】:

  • 是的,但为什么 *(a+0) 没有打印 a[0][0] 的值,它正在打印它的 ADDRESS。
  • @user8740705:因为*(a + 0)等价于a[0],而a[0]类型int [2]。由于它不是sizeof 或一元& 运算符的操作数,它“衰减”为指向第一个元素的指针,因此a[0] 等价于&a[0][0]
  • @JohnBode:a[0] 的类型实际上会根据它是表达式中的左值还是右值而改变。作为左值,它是 int[2],但作为右值,它是 int *。尝试输入一行“a[0] = a[1]”,看看会发生什么。您会认为它们必须具有相同的类型,但事实并非如此。是的,这很令人困惑,我不想让答案复杂化。
【解决方案2】:

除非它是sizeof 或一元& 运算符的操作数,或者是用于在声明中初始化字符数组的字符串文字,否则表达式 类型为“N- T"的元素数组被转换("decays")为类型为"pointer to T"的表达式,表达式的值是数组第一个元素的地址。

所以给定一个类似的声明

int a[100];

任何时候a 出现在不是sizeof 或一元& 运算符的操作数的表达式中,编译器会将其视为等效于&a[0] 的指针表达式(类型int * )。

现在,如果a 是一元& 的操作数会怎样?

数组的地址和数组的第一个元素的地址是一样的,从下图应该就清楚了:

   +------+
a: | a[0] |
   +------+
   | a[1] |
   +------+
     ...
   +------+
   | a[99]|
   +------+

表达式&a类型int (*)[100](指向int的100个元素数组的指针),但表达式的是与a&a[0] 相同(第一个元素的地址)。

用表格总结一下:

int a[100];

Expression         Type          "Decays" to          
----------         ----          -----------           
         a         int [100]     int *                 
        *a         int           n/a                   
        &a         int (*)[100]  n/a                   
      a[i]         int           n/a                   
     &a[i]         int *         n/a

同样,数组的地址与数组的第一个元素的地址相同,因此&aa&a[0] 都将产生相同的地址值(以任何类型转换为模) .

将 1 加到一个指针会产生指向类型的下一个对象的地址。 IOW,如果p 指向一个4 字节的int 对象,那么p + 1 的结果将是下一个4 字节int 的地址。如果 p 指向一个 100 元素 array 的 4 字节 ints,则 p + 1 产生下一个 100 元素数组 int 的地址。

下标操作a[i]定义为*(a + i)——给定地址a,找到a后面的第i'对象的地址并取消引用结果。

这意味着*a 的值与a[0] - *a == *(a + 0) == a[0] 相同。

这如何应用于二维数组,如您的示例所示?

鉴于声明

int a[1][2];

表达式 a“衰减”从类型“int 的 2 元素数组的 1 元素数组”(int [1][2])到“@ 的 2 元素数组的指针” 987654363@" (int (*)[2])。表达式a 的值是第一个元素的地址,但第一个元素本身具有数组类型。因此,该值“衰减”为指向子数组第一个元素的指针。

这里有一个方便的总结表:

int a[1][2];

Expression         Type            "Decays" to          
----------         ----            -----------          
         a         int [1][2]      int (*)[2]           
        *a         int [2]         int *                
        &a         int (*)[1][2]   n/a 
      a[0]         int [2]         int *
     *a[0]         int             n/a
     &a[0]         int (*)[2]      n/a
   a[0][0]         int             n/a                 

同样,&aaa[0]&a[0]&a[0][0] 都将产生相同的值,因为 a[0][0] 的地址与 a[0] 的地址相同与a的地址相同。

【讨论】:

    【解决方案3】:

    C 并没有真正将多维数组与单维数组区别对待。 C中的数组只是数组的数组

    char a[2][3][4][5];
    

    array 2 of array 3 of array 4 of array 5 of char

    对于任何“T 的数组 A”,取消引用/下标的工作方式相同:

    • 将 A 衰减到第一个元素的地址(如果您要延迟/下标指针,则不执行任何操作)
    • 添加由sizeof(T)缩放的索引

    在 C 中使用取消引用/下标,当您谈到一个时,您就谈到另一个,因为 A[Index]Index[A] 被定义为与 *(A+Index)*(Index+A) 相同。

    6.5.2.1p2

    后缀表达式后跟方括号 [] 中的表达式是数组对象元素的下标名称。下标运算符[]的定义是E1[E2]等同于(*((E1)+(E2)))。由于适用于二元 + 运算符的转换规则,如果 E1 是数组对象(等效地,指向数组对象的初始元素的指针)并且 E2 是整数,则 E1[E2] 指定第 E2 个元素E1(从零开始计数)。

    由于char a[2][3][4][5]; a数组 2 的(char 数组 5 的数组 4 的数组 3)a[1] 会给你((char*)&a) + 1 * sizeof(char[3][4][5]),结果将有输入char[3][4][5]

    现在数组的特殊之处在于此——数组不是 C 中的一等对象。你不能拥有数组类型的 r 值。当您尝试获取一个时,例如通过将数组传递给函数或运算符,数组立即衰减为指向其第一个元素的指针,因此a[1]char[3][4][5] 类型立即更改为char(*)[4][5]

    6.5.2.1p3

    连续的下标运算符指定多维数组对象的一个​​元素。如果 E 是维度为 i x j x 的 n 维数组 (n >= 2)。 . . x k,然后 E(用作左值以外的值)被转换为指向具有维度 j x 的 (n - 1) 维数组的指针。 . . xk。如果一元 * 运算符显式地或作为下标的结果隐式地应用于此指针,则结果是引用的 (n - 1) 维数组,如果用作左值以外的数组,它本身将转换为指针。由此得出,数组以行优先顺序存储(最后一个下标变化最快)。

    这会递归地继续,直到您切掉所有维度(从右到左)并留下一个不会衰减的真实类型。实际上,中间数组的衰减意味着中间的 derefs/subscripts 并没有真正获取任何东西——它们只是对基地址的添加。

    char a[2][3][4][5]; 的一些示例:

    #include <stdio.h>
    char a[2][3][4][5];
    #define ASSERT_TP(Expr,Tp) _Generic(Expr,Tp: (char*)(Expr))
    int main()
    {
        printf("%zd\n", ASSERT_TP(a,char(*)[3][4][5]) 
                       - (char*)a); //0
        printf("%zd\n", ASSERT_TP(a[1],char(*)[4][5]) 
                       - (char*)a); //60 == 1 * (3*4*5)
        printf("%zd\n", ASSERT_TP(a[1][1],char(*)[5]) 
                       - (char*)a); //80 == 1 * (3*4*5) + 1 * (4*5)
    }
    

    应用于您的示例:

    int a[1][2] = {1,2}; // a decays to ptr to 1st element, 
                         //i.e. to `int (*a)[2]`
    printf("%p or %p\n",
            *(a+0),  // == a[0]; ((char*)&a) + 0*sizeof(int[2]); 
                     // type is int[2], which decays to int*
            a+0);    // == a (after decay); (char*)&a + 0*sizeof(int[2]); 
                     //type is still `int(*)[2]` (because no derefing)
    

    因为*(a+0) 中的 deref 还没有命中真正的类型,所以没有获取,只是对基指针的添加 与类型调整。由于加法添加了 0,因此值没有改变它保持与 a 衰减到指向其第一个元素的指针 (== a+0) 甚至 &amp;a 相同(这将具有相同的数字地址但它的类型是int (*)[1][2])。

    【讨论】:

      【解决方案4】:

      数组可以衰减指向其第一个元素的指针。在您的示例中,纯 a 将衰减为 &amp;a[0]。对于任何数组或指针a和索引i,表达式a[i]完全等于*(a + i)

      另外,如果你将数组布置成它在内存中的样子,它会是

      +---------+---------+ |一个[0][0] |一个[0][1] | +---------+---------+

      现在有了这些信息,我们可以开始转换您的printf 调用中的表达式。

      让我们从*(a + 0)开始:

      1. 因为等价,*(a + 0) 变为 a[0]
      2. 因为a[0] 是一个数组,它会衰减为指向其第一个元素的指针,即&amp;a[0][0]

      所以第一个参数等于&amp;a[0][0],即指向a[0][0]的指针。

      那么让我们来a + 0

      1. 表达式a + 0 等于&amp;*(a + 0)
      2. 因为数组/指针等价的&amp;*(a + 0)变成了&amp;a[0]

      &amp;a[0] 是指向a 的第一个元素的指针,它恰好在内存中与a[0][0] 相同的位置开始。

      这当然意味着指针&amp;a[0]&amp;a[0][0] 都指向相同的位置,并且输出将相等。 然而这两个指针的类型非常不同:

      • &amp;a[0][0] 的类型是指向int 的指针,即int *
      • &amp;a[0] 的类型是指向两个int 元素的数组的指针,即int (*)[2]

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-04-18
        • 2014-02-03
        • 2014-06-20
        • 2017-04-22
        • 2019-09-09
        • 1970-01-01
        • 2011-11-27
        相关资源
        最近更新 更多