【问题标题】:What exactly is the array name in c?c中的数组名称到底是什么?
【发布时间】:2014-08-19 12:03:30
【问题描述】:

我很难理解 C 中数组名称的类型和使用。这可能看起来很长,但请多多包涵。

我了解以下语句将a 声明为int [] 类型,即整数数组

int a[30];

虽然a 也指向数组的第一个元素,但*(a+2) 之类的东西是有效的。因此,使a 看起来像一个指向整数的指针。但实际上int []int* 的类型是不同的;而前者是数组类型,而后者是指向整数的指针

当将int [] 类型的变量传递给函数时,它也会被转换为int* 类型的变量;如C 数组通过引用传递(sizeof 运算符除外)。

这就是让我摇摆不定的地方。看看下面这段代码:

int main()
{
    int (*p)[3];
    int a[3] = { 5, 4, 6 };

    p = &a;

    printf("a:%d\t&a:%d\n",a,&a);
    printf("%d",*(*p + 2));
}

输出:

a:2686720       &a:2686720
6

那么,上面的代码是如何工作的呢?我有两个问题:

  1. a&a 具有相同的值。为什么?
  2. int (*p)[3]; 到底是做什么的?它声明了一个指向数组的指针,我知道这一点。但是,指向数组的指针指向数组第一个元素的指针数组的名称有何不同?

谁能澄清一下?我有很多困惑。

我知道我应该使用%p 作为占位符,而不是使用%d 来打印指针变量的值。因为使用整数占位符可能会打印截断的地址。但我只想保持简单。

【问题讨论】:

  • c-faq.com 第 6 节。
  • sizeof 不是函数。它是一个运算符。括号通常不是必需的。 sizeof(int) 需要它们的原因是因为 (int) 是一种类型。因此,例如,如果您有 int a[10],那么您可以像 sizeof a 一样使用 sizeof,而无需任何括号。
  • 您可以根据需要搜索 SO 帖子。如果您要证明sizeof 不是函数,请参阅C99 Standard,第 6.4.1 节,其中它被列为 关键字,因此不能是函数。另请参阅第 6.5.3 节(一元运算符)和第 6.5.3.4 节(sizeof 运算符)。
  • 声明int a[30]a的类型不是int [];这是int [30]。数组长度是类型的一部分。
  • 关于sizeof,即使不检查标准,你也应该能够意识到sizeof不可能是一个函数。实现 sizeof 所做的函数是不可能的:不仅需要的信息对 C 函数不可用,而且在编译时需要答案。只有编译器本身才能提供 sizeof 的结果。

标签: c arrays pointers memory-address


【解决方案1】:

其他答案已经解释了这个问题。我试图用一些图表来解释它。希望这会有所帮助。


当你声明一个数组时

int a[3] = {5, 4, 6}  

内存排列看起来像

现在回答你的问题:

  1. a&a 具有相同的值。如何?

您已经知道a 是数组类型,数组名称a 成为指向数组a 的第一个元素的指针(衰减后),即它指向地址0x100。注意0x100 也是内存块的起始地址(数组a)。而且你应该知道,一般来说,第一个字节的地址就是变量的地址。也就是说,如果一个变量有 100 个字节,那么它的地址就等于它的第一个字节的地址。

&a是整个内存块的地址,即数组a的地址。看图:

现在你可以理解为什么a&a 都具有相同的地址值,尽管它们的类型不同。

它到底做了什么int (*p)[3]; 声明一个指向数组的指针,我知道这一点。但是,指向数组的指针与指向数组第一个元素的指针和数组名称有何不同?

看上图,清楚地解释了指向数组的指针与指向数组元素的指针有何不同。
当您将&a 分配给p 时,p 指向具有起始地址0x100 的整个数组。


注意:关于行

... 在C 中,数组通过引用传递(sizeof 函数除外)。

在 C 中,参数是按值传递的。 C中没有引用传递。当一个普通的变量被传递给一个函数时,它的值是复制的;对相应参数的任何更改都不会影响变量。
数组也是按值传递的,但不同的是数组名衰减为指向第一个元素的指针,而这个指针分配给函数的参数(这里是指针值被复制);数组本身不会被复制。
与普通变量相比,用作参数的数组不受任何更改的保护,因为没有复制数组本身,而是复制了指向第一个元素的指针。

您还应该注意,sizeof 不是函数,在这种情况下,数组名称不充当参数。 sizeof 是一个操作符,数组名作为一个操作数。当数组名称是一元 & 运算符的操作数时也是如此。

【讨论】:

  • 奇怪的是,我找不到任何“法律”基础来支持“第一个字节的地址被称为变量的地址”的说法。当然,我想不出实现设计者采用其他方式的实际理由,但据我所知,标准并没有禁止这样做。因此,从纯粹的角度来看,printf("a:%p\t&a:%p\n", reinterpret_cast<void*>(a), reinterpret_cast<void*>(&a));可能打印两个不同的值。
  • @AndreyChernyakhovskiy;当您处理数组时,printf("a:%p\t&a:%p\n", reinterpret_cast<void*>(a), reinterpret_cast<void*>(&a)); 将始终打印相同的地址。它们不可能指向不同的位置。让我们暂时假设a&a 都指向不同的位置,分别是0x1000x300。现在你有了一个函数,它需要一个指向数组的指针,即int (*)[] 类型,让p 成为int (*)[] 类型的参数。在这种情况下,您必须将 &a 作为参数传递。要在函数中访问a 的元素,.. 继续
  • 你必须需要数组的基地址,即当且仅当(*p)a的地址相同时,(*p)[i]将给出数组的第i元素。但是,正如我们假设的那样,a&a 并不指向同一个位置,因此编译器将永远不会通过指针算法*p + 1 获得第一个元素的地址,当然它会给出0x504,并访问未分配的内存可能是灾难性的。
  • @hacks,你没有明白我的意思。该标准从未定义对象的地址是什么。实现将其定义为为对象保留的内存位置的第一个字节的地址是很自然的。但是假设一个厌恶人类的设计者希望将其定义为 last 字节的地址。标准中是否有任何条款禁止这样做?据我所知,没有。当然,某些类型转换(包括数组类型衰减)将不得不调整地址,但如果准确完成,定义的语义将被保留。
  • @AndreyChernyakhovskiy; 标准中是否有任何条款禁止这样做?:没有。我明白你的意思。但是你可以说这是一个普遍的约定。我在回答中添加了:一般而言,以避免混淆。
【解决方案2】:
  1. a 和 &a 具有相同的值。如何?

它们具有相同的值但类型不同。数组对象在元素之间(之前或之后)没有填充,因此数组的地址和数组的第一个元素的地址相同。

即:

(void *) a == (void *) &a
  1. int (*p)[3] 的具体作用是什么;声明一个指向数组的指针,我知道这一点。但是,指向数组的指针与指向数组第一个元素的指针和数组名称有何不同?

这是两种不同的指针类型。以指针算法为例:

a + 1   /* address of the second element of the array */
&a + 1  /* address one past the last element of the array */

编辑:由于大众的需求,我在下面添加了一些关于数组转换的信息。

除了三个例外,在表达式中,T 类型数组的对象被转换为指向T 类型指针的值,该指针指向数组的第一个元素。例外情况是对象是 sizeof& 一元运算符的操作数,或者对象是初始化数组的字符串文字。

例如这个语句:

printf("a:%d\t&a:%d\n", a, &a);

实际上等价于:

printf("a:%d\t&a:%d\n", &a[0], &a);

另外请注意d 转换说明符只能用于打印有符号整数;要打印指针值,您必须使用 p 说明符(并且参数必须是 void *)。所以要正确使用:

printf("a:%p\t&a:%p\n", (void *) a, (void *) &a);

分别:

printf("a:%p\t&a:%p\n", (void *) &a[0], (void *) &a);

【讨论】:

  • 未能解释数组表达式到指向其第一个元素的指针的隐式转换,以及不发生这种转换的情况。
  • 我不同意 int (*p)[3] 声明了一个包含 3 个指向 int 的指针的数组。如果原始代码只是简单地声明了 int *p;代码仍然可以工作。 p[1]和p[2]在数组a的地址赋值后仍然为空。 p[0] == &a, p[1] 和 p[2] 将是 0 或未确定,具体取决于编译器。
  • @JeffD。您不能将类型 int (*)[] 分配给整数指针。
  • @KeithThompson 以a 为例,询问指向数组的指针与指向数组第一个元素和数组名称的指针有何不同 &a OP 表明他知道从数组到数组第一个元素的隐式转换。
  • @KeithThompson。我知道将数组类型隐式转换为指向数组第一个元素的指针的情况。
【解决方案3】:
  1. a 对应于指向数组第 0 个元素的指针。而 &a 也是如此。它只是给出了数组的起始地址。

作为,a --> pointer pointing to starting element of array a[],it does not know about other element's location..

&a --->address location for storing array a[] which stores first element location,but knows every element's location.

同样,其他元素的位置将是 (a+2)、(a+4) 等等,直到数组的末尾。

因此,你得到了这样的结果。

  1. int (*p)[3] 是指向数组的指针。如果它是 int *p[3],它的含义就完全不同了。这意味着一个指针数组,它与这个上下文完全不同。

指向数组的指针会自动处理所有其他的 数组中的元素。在这种情况下,你的是 (p);

而指向数组第一个元素的指针,即a will 只知道数组的第一个元素。你必须手动 给出指针算术方向以访问下一个元素。见,在这个 案例---我们可以通过将 2 添加到 a 来从 a 中获取第二个元素,即 a+2,第三个元素,通过将 4 加到 a,即 a+4 等等。 // 注意 两个的差,因为它是一个整数数组!

【讨论】:

    【解决方案4】:

    在回答问题 1 时,这只是 C 语言设计的一个方面,与大多数其他现代语言不同,C/C++ 允许直接操作内存中的地址,并内置了“理解”它的设施。网上有很多文章比我在这个狭小的空间里能更好地解释这一点。这是一个,我相信还有很多其他的:http://www.cprogramming.com/tutorial/c/lesson8.html

    【讨论】:

    • 那个教程没有正确解释数组和指针的关系。
    【解决方案5】:

    来自C99 Standard n1124 6.3.2.1 p3

    除非它是 sizeof 运算符的操作数或一元 & 运算符,或者是用于初始化数组的字符串文字, 具有“类型数组”类型的表达式被转换为 类型为“类型指针”的表达式,指向初始 数组对象的元素并且不是左值。如果数组对象 有注册存储类,行为未定义。

    【讨论】:

      【解决方案6】:

      a 和 &a 具有相同的值,因为很久以前您需要在数组上使用地址运算符 & 来获取数组的地址,但现在不再需要了。现在数组的名称(在本例中为 a)仅代表数组本身的内存地址,这也是您从 &a 中得到的。这是编译器为您处理的简写。

      【讨论】:

      • no a&a 是不同的东西,即上面的帖子所说的。
      • black_perl,它们在较低级别上是不同的,但他想知道为什么它们具有相同的值,因为它们都返回数组前面的地址,这就是重点。除非您要补充说明,否则无需复杂解释。
      猜你喜欢
      • 2017-08-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-29
      • 1970-01-01
      • 2011-03-06
      • 2017-10-12
      相关资源
      最近更新 更多