是a,&a,*a,a [0],&a [0]和&a [0] [0]相同的指针?

Kod*_*rok 8 c arrays pointers multidimensional-array

我有以下C程序:

#include <stdio.h>

int main(){
    int a[2][2] = {1, 2, 3, 4};
    printf("a:%p, &a:%p, *a:%p \n", a, &a, *a);
    printf("a[0]:%p, &a[0]:%p \n", a[0], &a[0]);
    printf("&a[0][0]:%p \n", &a[0][0]);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

它给出了以下输出:

a:0028FEAC, &a:0028FEAC, *a:0028FEAC
a[0]:0028FEAC, &a[0]:0028FEAC
&a[0][0]:0028FEAC
Run Code Online (Sandbox Code Playgroud)

我无法理解为什么&a,a,*a-所有相同.同样的a[0],&a[0]&a[0][0].

编辑:

感谢答案,我已经理解为什么这些价值观会相等.Kernighan&Ritchie的书中的这一行被证明是我问题的关键:

 the name of an array is a synonym for the location of the initial element.
Run Code Online (Sandbox Code Playgroud)

所以,通过这个,我们得到了

a= &a[0],和

a[0]= &a[0][0] (考虑a作为数组的数组)

直观地说,现在输出背后的原因很明显.但是,考虑到如何在C中实现指针,我无法理解如何a并且&a是平等的.我假设a内存中有一个指向数组的变量(此数组内存块的起始地址将是此变量的值a).

但是,当我们这样做时&a,这是不是意味着获取存储变量的内存位置的地址a?为什么这些值相等呢?

Kei*_*son 15

他们不是相同的指针.它们是指向相同内存位置的不同类型的指针.相同的值(种类),不同的类型.

C中的二维数组不过是数组数组.

对象a是类型int[2][2],或2元素数组的2元素数组int.

在大多数但不是所有上下文中,数组类型的任何表达式都隐式转换为("衰减")指向数组对象的第一个元素的指针.所以表达式 a,除非它是一元的操作数,&或者sizeof是类型int(*)[2],并且相当于&a[0](或者&(a[0])如果更清楚).它成为指向二维数组的第0行的指针.重要的是要记住这是一个指针(或等效地址),而不是指针对象 ; 除非您明确创建指针对象,否则此处没有指针对象.

所以看看你问过的几个表达方式:

  • &a是整个数组对象的地址; 它是类型的指针表达式int(*)[2][2].
  • a是数组的名称.如上所述,它"衰减"到指向数组对象的第一个元素(行)的指针.它是类型的指针表达式int(*)[2].
  • *a 取消引用指针表达式a.由于a(在衰减之后)是指向2 ints 数组的指针,因此*a是一个2 ints 的数组.由于这是一个数组类型,它会衰减(在大多数但不是所有上下文中)指向数组对象的第一个元素的指针.所以这是类型int*.*a相当于&a[0][0].
  • &a[0]是数组对象的第一行(第0行)的地址.这是类型int(*)[2].a[0]是一个数组对象; 它不会衰减到指针,因为它是一元的直接操作数&.
  • &a[0][0]是数组对象的第0行的元素0的地址.这是类型int*.

所有这些指针表达式都指向内存中的相同位置.该位置是数组对象的开头a; 它也是数组对象的开始a[0]和的int对象a[0][0].

打印指针值正确的方法是使用"%p"的格式,并把指针值转换为void*:

printf("&a = %p\n", (void*)&a);
printf("a  = %p\n", (void*)a);
printf("*a = %p\n", (void*)*a);
/* and so forth */
Run Code Online (Sandbox Code Playgroud)

此转换void*产生一个"原始"地址,该地址仅指定内存中的位置,而不是该位置的对象类型.因此,如果您有多个不同类型的指针指向从同一内存位置开始的对象,则将它们全部转换void*为相同的值.

(我已经掩盖了[]索引操作符的内部工作方式.表达式x[y]根据定义等效*(x+y),其中x是一个指针(可能是数组隐式转换的结果)并且y是一个整数.反之亦然,但这很难看; arr[0]并且0[arr]是等价的,但只有在你编写故意混淆的代码时才有用.如果我们考虑到这种等价,那么需要一段左右的时间来描述具体的a[0][0]方法,这个答案可能已经太长了.)

为了完整起见,数组类型的表达式隐式转换为指向数组第一个元素的指针的三个上下文是:

  • 当它是一元的操作数时&,则&arr产生整个数组对象的地址;
  • 当它是操作数时sizeof,sizeof arr产生数组对象的字节大小,而不是指针的大小; 和
  • 如果它是用于初始化数组(子)对象的初始值设定项中的字符串文字,char s[6] = "hello";则将数组值复制到s而不是无意义地使用指针值初始化数组对象.最后一个异常不适用于您要询问的代码.

(2011年ISO C标准的N1570草案错误地指出这_Alignof是第四个例外;这是不正确的,因为_Alignof只能应用于带括号的类型名称,而不是表达式.错误在最终的C11标准中得到纠正.)

推荐阅读:comp.lang.c FAQ的第6部分.

  • @haccks:`a [i]`是一个类型为`int [2]`的数组对象,它是`a`的行`i`.在大多数情况下,表达式"a [i]"衰减到指向该数组的第一个元素的指针,即指向`int`对象`a [i] [0]`的指针.但是在`&a [i]`中,因为`a [i]`是一元`&`的操作数,它不会衰减,而`&a [i]`是数组对象的地址`a [i] `,并且类型为`int(*)[2]`.不,`&a [i]`和`a [i]`不相同; 前者是`a`的行`i`的地址,而`a [i]`是*该行的第一个元素的行或地址,具体取决于上下文. (2认同)

Tom*_*r W 6

因为所有表达式都指向数组的开头:

a = {{a00},{a01},{a10},{a11}}
Run Code Online (Sandbox Code Playgroud)

a 指向数组,只是因为它是一个数组,所以 a == &a[0]

并且&a[0][0]位于2D阵列的第一个单元格处.


Lid*_*Guo 5

 +------------------------------+
 | a[0][0]   <--   a[0] <--   a | // <--&a, a,*a, &a[0],&a[0][0] 
 |_a[0][1]_                     |
 | a[1][0]   <--   a[1]         |
 | a[1][1]                      |
 +------------------------------+
Run Code Online (Sandbox Code Playgroud)


Uch*_*chi 5

它打印出相同的值,因为它们都指向相同的位置.

话说回来,

&a[i][i]int *一个指向整数的指针的类型.

a并且&a[0]具有int(*)[2]指示指向2int 数组的指针的类型.

&a其类型int(*)[2][2]指示指向一个2-D array或两个元素数组的指针的指针,其中每个元素是一个2-int的数组.

所以,如果你开始对它们进行指针运算,它们都是不同类型的,并且表现不同.

(&a[0][1] + 1) 指向2-D数组中的下一个整数元素,即 a[0][1]

&a[0] + 1 指向下一个整数数组,即 a[1][0]

&a + 1指向下一个在这种情况下不存在的二维数组,但a[2][0]如果存在则存在.

  • +1指出它们是三种不同的类型,但恰好具有相同的值。 (2认同)
  • @haccks:`int(*)[2]`是指针类型,所以是的,它与数组类型`int [2] [2]`不同.*object*`a`的类型为`int [2] [2]`.*表达式*`a`在大多数但不是所有上下文中被隐式转换为指向数组对象的第一个元素的指针,产生类型为`int(*)[2]`的表达式.但是`sizeof a`与`sizeof(int [2] [2])`相同. (2认同)