9

我有以下 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书中的这句话原来是我问题的关键:

 the name of an array is a synonym for the location of the initial element.

所以,由此,我们得到

a= &a[0], 和

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

直觉上,现在输出背后的原因很清楚了。但是,考虑到指针是如何在 C 中实现的,我无法理解它们是如何a相等&a的。我假设a内存中有一个指向数组的变量(并且此数组内存块的起始地址将是此变量的值a)。

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

4

8 回答 8

17

它们不是相同的指针。它们是指向同一个内存位置的不同类型的指针。相同的值(某种),不同的类型。

C 中的二维数组只不过是一个数组数组。

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

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

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

  • &a是整个数组对象的地址;它是 type 的指针表达式int(*)[2][2]
  • a是数组的名称。如上所述,它“衰减”为指向数组对象第一个元素(行)的指针。这是一个类型的指针表达式int(*)[2]
  • *a 取消引用指针表达式a。因为a(在它衰减之后)是一个指向 2 ints数组的指针,所以*a是一个 2 s 数组int。由于这是一个数组类型,它(在大多数但不是所有上下文中)衰减为指向数组对象第一个元素的指针。所以它是 type int**a相当于&a[0][0]
  • &a[0]是数组对象的第一(0)行的地址。它的类型int(*)[2]a[0]是一个数组对象;它不会衰减为指针,因为它是 unary 的直接操作数&
  • &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 */

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

(我已经忽略了[]索引运算符的内部工作原理。根据x[y]定义,表达式等同于*(x+y),其中x是指针(可能是数组的隐式转换的结果)并且y是整数。反之亦然,但这很难看;arr[0]并且0[arr]是等价的,但这仅在您编写故意混淆的代码时才有用。如果我们考虑到这种等价性,则需要一段左右的时间来描述什么a[0][0]意思,而这个答案可能已经太长了。)

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

  • 当它是 unary 的操作数时&&arr产生整个数组对象的地址;
  • 当它是 的操作数时sizeofsizeof arr产生数组对象的字节大小,而不是指针的大小;和
  • 当它是用于初始化数组(子)对象的初始化程序中的字符串文字时,因此char s[6] = "hello";将数组值复制到s而不是用指针值无意义地初始化数组对象。最后一个例外不适用于您询问的代码。

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

推荐阅读: comp.lang.c FAQ的第 6 节。

于 2013-08-21T18:04:11.543 回答
6

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

a = {{a00},{a01},{a10},{a11}}

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

并且&a[0][0]位于二维数组的第一个单元格处。

于 2013-08-21T15:11:52.260 回答
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]                      |
 +------------------------------+
于 2013-08-21T15:15:36.763 回答
5

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

话说回来,

&a[i][i]int *是指向整数的指针类型。

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

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

因此,如果您开始对它们进行指针运算,它们都是不同的类型并且行为不同。

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

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

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

于 2013-08-21T15:30:14.083 回答
3

您知道这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

于 2013-08-21T15:14:42.330 回答
3

C 中的二维数组被视为一维数组,其元素是一维数组(行)。
例如,一个 4x3 数组T(其中“T”是某种数据类型)可以由: 声明 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]到我们有一个类型为元素a的数组。但是,名称本身是一个数组,表示存在元素,每个元素都是一个元素数组。因此,我们每个都有一个元素数组。 现在很明显指向 的第一个元素 ( ) 。另一方面,将给出第一个元素 ( ) 的地址,并将给出行 ( ) 数组的地址。将给出二维数组的地址。3Ta[4]4343
aa[0]a[4]&a[0]a[0]a[4]&a[0][0]0tha00 | a01 | a02)a[4][3]&aa[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])。
    但是组G1G2G3并不相同,尽管它们给出了相同的结果(我在上面解释了为什么它给出了相同的结果)。
于 2013-08-21T15:33:00.703 回答
2

这也意味着在 C 数组中没有开销。在其他一些语言中,数组的结构是

&a     -->  overhead
            more overhead
&a[0]  -->  element 0
            element 1
            element 2
            ...

&a != &a[0]

于 2013-08-21T15:18:12.777 回答
2

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

嗯,不。在内存中的任何地方都没有存储地址之类的东西。只为原始数据分配了内存,仅此而已。发生的情况是,当您使用裸的时a,它会立即衰减为指向第一个元素的指针,给人的印象是 的“值”a是地址,但唯一的值a是原始数组存储。

事实上,a&a是不同的,但只是在类型上,而不是在价值上。让我们通过使用一维数组来澄清这一点,让它变得更容易一些:

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行为非常不同,即使它们共享相同的值。

于 2013-09-14T15:40:24.573 回答