【问题标题】:Are a, &a, *a, a[0], &a[0] and &a[0][0] identical pointers?a, &a, *a, a[0], &a[0] 和 &a[0][0] 是相同的指针吗?
【发布时间】:2013-08-24 01:01:14
【问题描述】:

我有以下 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

我无法理解为什么&amp;aa*a - 都相同。 a[0]&amp;a[0]&amp;a[0][0] 也是如此。

编辑:

多亏了答案,我明白了这些值相等的原因。 Kernighan & Ritchie 书中的这句话原来是我问题的关键:

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

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

a = &amp;a[0],和

a[0] = &amp;a[0][0](将a 视为数组数组)

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

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

【问题讨论】:

  • &amp;a[0]&amp;a[0][0] 不相同。
  • 你应该使用%p来打印指针,并转换为void *
  • [为什么 A[0]、&A 和 *A 输出的值相同](stackoverflow.com/q/17623556/2455888) 的可能重复项。
  • @hacks: 但是为什么我在打印它们时会得到相同的值呢?
  • @Pulkit Yadav:当你问一个关于以这种方式而不是以其他方式工作的问题时,你必须解释为什么你觉得它很奇怪。你的输出中没有什么出乎意料的,所以你的问题看起来真的像“为什么 2+2 是 4”。你希望人们如何回答这样的问题?说明您对该输出有哪些具体问题,更重要的是,说明您为何将它们视为“问题”,我们会解决这些问题。

标签: c arrays pointers multidimensional-array


【解决方案1】:

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

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

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

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

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

  • &amp;a是整个数组对象的地址;它是int(*)[2][2] 类型的指针表达式。
  • a 是数组的名称。如上所述,它“衰减”为指向数组对象第一个元素(行)的指针。这是一个int(*)[2] 类型的指针表达式。
  • *a 取消引用指针表达式a。因为a(衰减后)是一个指向2个ints数组的指针,所以*a是2个ints数组。由于这是一个数组类型,它(在大多数但不是所有上下文中)衰减为指向数组对象第一个元素的指针。所以它的类型是int**a 等价于 &amp;a[0][0]
  • &amp;a[0] 是数组对象的第一(0)行的地址。它的类型为int(*)[2]a[0] 是一个数组对象;它不会衰减为指针,因为它是一元 &amp; 的直接操作数。
  • &amp;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]的意思,这个答案可能已经太长了。)

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

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

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

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

【讨论】:

  • 我读到a[i] 给出了ith 行的地址。现在我认为&amp;a[i]a[i] 相同。不是吗?
  • @hacks: a[i] 是一个int[2] 类型的数组对象,它是i 的行a。在大多数情况下,表达式a[i] 衰减为指向该数组第一个元素的指针,即指向int 对象a[i][0] 的指针。但在&amp;a[i]中,由于a[i]是一元&amp;的操作数,它不会衰减,而&amp;a[i]是数组对象a[i]的地址,是int(*)[2]类型。不,&amp;a[i]a[i] 不相同;前者是ai 行的地址,a[i] 是该行或该行的第一个元素的地址,具体取决于上下文。
  • 多年前我做了一张图表,说明这三个指针(或足够相似的指针)之间的区别,即here。另一种看待这一点的方式是,它们指向相同的基地址,但指向该基地址处的不同“大小单位”。
  • 好的。让我们再继续一遍。我有两个问题: 1. a[i] 是该行还是该行的第一个元素的地址是什么意思,取决于上下文? 2. 你说a equivalent&amp;a[0]*a equivalent&amp;a[0][0],我可以替换这个词 equivalent 相同
  • @hacks: a[i] 是一个数组类型的表达式(具体来说,它的类型是int[2])。如果a[i] 是一元&amp;sizeof 的操作数,则指向该数组对象;在任何其他上下文中,它都会隐式转换为该数组对象的第一个元素的地址。 2. 我想是的;它们是相同类型和值的表达式,但它们的写法不同。我认为“等效”效果更好一些。 (2+2) 相同4吗?
【解决方案2】:

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

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

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

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

【讨论】:

    【解决方案3】:
     +------------------------------+
     | 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]                      |
     +------------------------------+
    

    【讨论】:

      【解决方案4】:

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

      话虽如此,

      &amp;a[i][i]int * 类型,它是一个指向整数的指针。

      a&amp;a[0] 的类型为 int(*)[2],表示指向 2 整数数组的指针。

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

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

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

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

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

      【讨论】:

      • +1 指出它们是碰巧具有相同值的三种不同类型。
      • a 不是 int ** 类型的吗?通过*a 取消引用a 将打印a[0] 的值,该值属于int * 类型。另一方面,使用**a 解引用它两次将打印a[0][0](整数)的值。
      • object aint[2][2] 类型,它是数组类型,而不是指针类型。 表达式 a,除非它是一元&amp;sizeof 的操作数,在隐式转换之后,是int(*)[2] 类型,或指向@987654350 的2 元素数组的指针@。 a,转换后相当于&amp;a[0](或者&amp;(a[0]),如果更清楚的话)。 @PulkitYadav:不,它不是int** 类型;没有可以指向的 int* 对象。
      • @hacks: int (*)[2] 是指针类型,所以是的,它与数组类型 int[2][2] 不同。 对象 aint[2][2] 类型。 表达式 a 在大多数但不是所有上下文中被隐式转换为指向数组对象第一个元素的指针,从而产生int(*)[2] 类型的表达式。但例如sizeof asizeof (int[2][2]) 相同。
      • (不小心点击了聊天按钮。)我会说“在将其转换为指针的任何上下文中使用时”,而不是“用作指针时”。 (我想“用作指针”是暗示的。)
      【解决方案5】:

      您知道a 是数组第一个元素的地址,根据C 标准,a[X] 等于*(a + X)

      所以:

      &amp;a[0] == a 因为&amp;a[0]&amp;(*(a + 0)) = &amp;(*a) = a 相同。

      &amp;a[0][0] == a 因为&amp;a[0][0]&amp;(*(*(a + 0) + 0))) 相同 = &amp;(*a) = a

      【讨论】:

      • 你第一次是对的 - a[X] 等于 *(a + X)。指针算法已经考虑到它指向的类型的大小。
      • "&amp;a[0][0]&amp;(*(a + 0 + 0))" 不一样。它们甚至没有相同的类型。 &amp;a[0][0]&amp;(*(*(a + 0) + 0))) 相同
      • @newacct 我不确定。起初我写了&amp;(*(*(a + 0) + 0))),但我不知道如何证明&amp;(*(*(a + 0) + 0))) = a 实际上......你怎么能这样做?
      • @nouney:嗯,它们相同,因为它们有不同的类型。但是,它们的地址是相同的。首先,a(一个数组表达式)在大多数情况下都会隐式转换为&amp;a[0],所以这就是第一个为真的原因。 &amp;a[0]&amp;a[0][0] 具有相同的地址值,因为a[0] 是一个数组,从数组在C 中的工作方式可以明显看出这一定是真的。 (注意:如果a[0] 是一个指针,这将是不正确的。所以它是一个数组这一事实很重要。)
      • @newacct 哦,是的,我知道它们有不同的类型,我只是想以“数学方式”(这似乎不可能)证明两者的地址是相同的 :) 感谢您的解释!
      【解决方案6】:

      C 中的二维数组被视为一维数组,其元素是一维数组(行)。
      例如,T 的 4x3 数组(其中“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 我们有一个3 类型为T 的元素数组。但是,名称a[4] 本身是一个数组,表示存在4 元素,每个元素都是3 元素的数组。因此,我们有一个 4 数组,每个数组都有 3 元素。
      现在很明显 a 指向 a[4] 的第一个元素 (a[0])。另一方面,&amp;a[0] 将给出a[4] 的第一个元素(a[0])的地址,&amp;a[0][0] 将给出0th 行的地址(a00 | a01 | a02) 数组a[4][3]&amp;a 将给出二维数组的地址a[3][4]*a 衰减为指向a[0][0] 的指针。
      注意a不是指向a[0][0]的指针;相反,它是指向a[0] 的指针。
      因此

      • G1:a&amp;a[0] 是等效的。
      • G2:*aa[0]&amp;a[0][0]是等价的。
      • G3:&amp;a(给出二维数组a[3][4]的地址)。
        但是组 G1G2G3 并不相同,尽管它们给出了相同的结果(我在上面解释过为什么它给出相同的结果)。

      【讨论】:

      • 对不起,图片还不清楚。您是否交替使用mata?你说mat是一个指向mat[0]的指针,但是在图中,你已经显示了mat == mat[0](这不是说它们相等吗?)。此外,这似乎不太正确:“mat[0][0] 将给出数组 mat[4][3] 的第 0 行的地址”。 mat[0][0] 不是给出内容而不是地址吗?
      • 好的,谢谢!您能否在之前的评论中回答我的其他问题。
      • 是的。你是对的mat[0][0] 会给出内容而不是地址。是打字错误。我的错。
      • 如果您认为 2D 数组是 1D 即 a[3][4]a[4]:有 4 个元素,每个元素都是 3 个元素的数组,那么 a 将指向数组的第一个元素 @ 987654368@。现在可以说a&amp;a&amp;a[0] 相同,但与a[0]&amp;a[0][0] 不同。 a[0]a[0][0] 相同。
      • 谢谢!这很清楚。我现在只需要了解一件事——对于任何通用数组a(比如一维数组),为什么a&amp;a 相同?
      【解决方案7】:

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

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

      &amp;a != &amp;a[0]

      【讨论】:

        【解决方案8】:

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

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

        其实a&amp;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&amp;a 的行为非常不同,尽管它们共享相同的值。

        【讨论】:

          猜你喜欢
          • 2017-03-12
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-03-20
          • 1970-01-01
          • 1970-01-01
          • 2023-03-15
          相关资源
          最近更新 更多