a,&a,* a,a ,&a 和&a 是否相同的指针?
我有以下的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; }
它给出了以下输出:
a:0028FEAC, &a:0028FEAC, *a:0028FEAC a[0]:0028FEAC, &a[0]:0028FEAC &a[0][0]:0028FEAC
我无法理解为什么&a
, a
, *a
都完全相同。 对于a[0]
, &a[0]
和&a[0][0]
。
编辑:
多亏了答案,我明白了为什么这些价值观是平等的。 Kernighan&Ritchie的书中的这一行certificate是我的问题的关键:
the name of an array is a synonym for the location of the initial element.
所以,通过这个,我们得到了
a
= &a[0]
,和
a[0]
= &a[0][0]
(考虑作为数组的数组)
直觉上,现在原因很清楚。 但是,考虑如何在C中实现指针,我不明白a
和&a
是如何相等的。 我假设在内存中有一个指向数组的variablesa
(并且此数组的内存块的起始地址将是此variablesa
的值)。
但是,当我们这样做&a
,这是不是意味着存储variablesa
的内存地址呢? 为什么这些值是相等的呢?
他们不是相同的指针。 它们是指向不同types的指针,指向相同的内存位置。 相同的值(有点),不同的types。
C中的二维数组只不过是一个数组数组。
对象a
的types是int[2][2]
,或者是2元素的int
数组的2元素数组。
数组types的任何expression式在大多数但不是所有的上下文中都隐式转换为(“decay”)指向数组对象的第一个元素的指针。 因此, expression式 a
除非是一元&
或sizeof
的操作数,否则为int(*)[2]
,如果更清楚,则相当于&a[0]
(或&(a[0])
)。 它变成了二维数组0行的指针。 重要的是要记住,这是一个指针值 (或等价的地址 ),而不是一个指针对象 ; 这里没有指针对象,除非你明确地创build一个指针对象。
所以看看你问的几个expression式:
-
&a
是整个数组对象的地址; 它是一个int(*)[2][2]
types的指针expression式。 -
a
是数组的名称。 正如上面所讨论的,它会“衰减”到指向数组对象的第一个元素(行)的指针。 这是一个int(*)[2]
types的指针expression式。 -
*a
指针expression式a
解除引用 由于a
(在衰减之后)是一个2int
的数组的指针,所以*a
是一个2int
s的数组。 因为这是一个数组types,所以它会衰减(在大多数情况下,但不是在所有情况下)到指向数组对象的第一个元素的指针。 所以它是int*
types的。*a
相当于&a[0][0]
。 -
&a[0]
是数组对象的第一行(第0行)的地址。 它的types是int(*)[2]
。a[0]
是一个数组对象; 它不会衰减到指针,因为它是一元&
的直接操作数。 -
&a[0][0]
是数组对象的第0行的元素0的地址。 它是int*
types的。
所有这些指针expression式都指向内存中的相同位置。 那个位置是数组对象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 */
这种转换为void*
产生一个“原始”地址,该地址仅指定内存中的某个位置,而不指定该位置的对象types。 所以,如果你有多个不同types的指针指向在相同内存位置开始的对象,将它们全部转换为void*
产生相同的值。
(我已经掩盖了[]
索引操作符的内部工作,expression式x[y]
的定义相当于*(x+y)
,其中x
是一个指针(可能是数组隐式转换的结果), y
是一个整数,反之亦然,但这很丑陋; arr[0]
和0[arr]
是等价的,但是只有当你编写故意混淆的代码时才有用,如果我们考虑这个等价性,段落左右来描述a[0][0]
意味着什么,这个答案可能已经太长了。)
为了完整起见,将数组types的expression式不隐式转换为指向数组第一个元素的指针的三个上下文是:
- 当它是一元操作数
&
,所以&arr
产生整个数组对象的地址; - 当它是
sizeof
的操作数时,sizeof arr
产生数组对象的大小(以字节为单位),而不是指针的大小; 和 - 当它是用于初始化一个数组(子)对象的初始化器中的string文字时,所以
char s[6] = "hello";
将数组值复制到s
而不是用指针值非正确地初始化一个数组对象。 最后一个exception不适用于您所询问的代码。
(2011年ISO C标准的N1570草案错误地指出_Alignof
是第四个exception;这是不正确的,因为_Alignof
只能应用于括号内的types名称,而不是expression式,错误在最终的C11标准中得到纠正。 )
推荐阅读: comp.lang.c FAQ的第6部分。
因为所有expression式都指向数组的开头:
a = {{a00},{a01},{a10},{a11}}
指向数组,只是因为它是一个数组,所以a == &a[0]
和&a[0][0]
位于2Darrays的第一个单元格中。
它打印出相同的值,因为它们都指向相同的位置。
话说回来,
&a[i][i]
的types是int *
,它是一个指向整数的指针。
a
和&a[0]
的types为int(*)[2]
,表示指向2
整数的指针。
&a
的types是int(*)[2][2]
,表示指向2-D array
的指针或指向两个元素的数组的指针,其中每个元素都是2个数组的数组。
所以,如果你开始对它们进行指针运算的话,它们都是不同types的,行为也不一样。
(&a[0][1] + 1)
指向二维数组中的下一个整数元素,即指向a[0][1]
&a[0] + 1
指向下一个整数数组,即指向a[1][0]
&a + 1
指向在这种情况下不存在的下一个二维数组,但是如果存在则a[2][0]
。
+------------------------------+ | 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] | +------------------------------+
你知道a
是数组的第一个元素的地址,根据C标准, a[X]
等于*(a + X)
。
所以:
&a[0] == a
因为&a[0]
与&(*(a + 0))
= &(*a)
= a
。
&a[0][0] == a
因为&a[0][0]
与&(*(*(a + 0) + 0)))
= &(*a)
= a
C中的二维数组被视为一维数组,其元素是一维数组(行)。
例如,一个T
的4×3数组(其中“T”是某种数据types)可以用下面的公式表示: T a[4][3]
+-----+-----+-----+ a == a[0] ---> | a00 | a01 | a02 | +-----+-----+-----+ +-----+-----+-----+ a[1] ---> | a10 | a11 | a12 | +-----+-----+-----+ +-----+-----+-----+ a[2] ---> | a20 | a21 | a22 | +-----+-----+-----+ +-----+-----+-----+ a[3] ---> | a30 | a31 | a32 | +-----+-----+-----+
数组元素也被存储在内存行中。
在T
加上[3]
,我们有一个3
T
型元素的数组。 但是,名称a[4]
本身就是一个数组,指示有4
元素,每个元素都是3
元素的数组。 因此,我们有一个由3
元素组成的数组。
现在很清楚,指向a[4]
的第一个元素( a[0]
)。 另一方面&a[0]
将给出a[4]
的第一个元素( a[0]
)的地址, &a[0][0]
将给出数组的0th
行的地址( a00 | a01 | a02)
a[4][3]
。 &a
将给出二维数组a[3][4]
。 *a
指向a[0][0]
指针衰减。
请注意, a
不是指向a[0][0]
的指针; 相反,它是一个指向a[0]
的指针。
于是
- G1:
a
和&a[0]
是等价的。- G2:
*a
,a[0]
和&a[0][0]
是等价的。- G3:
&a
(给出二维数组a[3][4]
)。
但G1组, G2组和G3组虽然给出了相同的结果 (我在上面解释了为什么给出了相同的结果), 但它们并不相同 。
这也意味着在C数组中没有开销。 在其他一些语言中,数组的结构是
&a --> overhead more overhead &a[0] --> element 0 element 1 element 2 ...
和&a != &a[0]
直觉上,现在原因很清楚。 但是,考虑如何在C中实现指针,我不明白a和a是如何相等的。 我假设在内存中有一个指向数组的variablesa(并且此数组的内存块的起始地址将是此variablesa的值)。
那么,不。 存储器中任何地方都没有地址。 只有内存分配给原始数据,就是这样。 会发生什么呢,当你使用一个裸体a
,它立即衰变成一个指向第一个元素的指针,给人的印象是a的值是地址,但是a的唯一值是原始数组的存储。
事实上, a
和&a
是不同的,但只是在types上,而不是在价值上。 通过使用一维数组来澄清这一点,让我们更容易一点:
bool foo(int (*a)[2]) { //a function expecting a pointer to an array of two elements return (*a)[0] == (*a)[1]; //a pointer to an array needs to be dereferenced to access its elements } bool bar(int (*a)[3]); //a function expecting a pointer to an array of three elements bool baz(int *a) { //a function expecting a pointer to an integer, which is typically used to access arrays. return a[0] == a[1]; //this uses pointer arithmetic to access the elements } int z[2]; assert((size_t)z == (size_t)&z); //the value of both is the address of the first element. foo(&z); //This works, we pass a pointer to an array of two elements. //bar(&z); //Error, bar expects a pointer to an array of three elements. //baz(&z); //Error, baz expects a pointer to an int //foo(z); //Error, foo expects a pointer to an array //bar(z); //Error, bar expects a pointer to an array baz(z); //Ok, the name of an array easily decays into a pointer to its first element.
正如你所看到的,即使它们具有相同的价值, a
&a
行为也会有所不同。