18

1) 误解

  • 每当在 C 语言中声明数组时,都会隐式创建指向数组第一个元素的指针(数组的名称)。(是吗?我不这么认为!)

  • 页面的前两行(尽管我不确定信息的正确性)陈述相同。

    正如我们所看到的,当我们声明一个数组时,会为数组的单元分配一个连续的内存块,并且还会分配一个指针单元(适当类型的)并初始化为指向数组的第一个单元。

  • 但是当我输出该指针中包含地址和该指针的地址,它们结果是相同的。所以,我认为毕竟没有创建指针。

2)我从这个问题中选择了这个。

  • 在大多数情况下,数组名称被转换为指针。

任何人都可以详细解释编译器何时决定数组名称转换为指针,以及为什么

PS:请与功能相同。同样在这个链接中,给出了一个例子,说对于一个函数,int square(int,int)任何 一个square,,,,都是指同一个函数指针。你可以解释吗? &square*square**square

编辑:代码片段

int fruits[10];
printf("Address IN constant pointer is %p\n",  fruits);
printf("Address OF constant pointer is %p\n", &fruits); 

输出 :

Address IN constant pointer is 0xbff99ca8
Address OF constant pointer is 0xbff99ca8
4

4 回答 4

35

数组类型的表达式被隐式转换为指向数组对象第一个元素的指针,除非它是:

  • 一元运算&符的操作数;
  • 的操作数sizeof;或者
  • 用于初始化数组对象的初始化程序中的字符串文字。

第三种情况的一个例子是:

char arr[6] = "hello";

"hello"是一个数组表达式,类型为char[6](5 加 1 表示'\0'终止符)。它没有转换为地址;of 的完整 6 字节值"hello"被复制到数组 objectarr中。

另一方面,在这个:

char *ptr = "hello";

数组表达式"hello"“衰减”为指向 的指针'h',并且该指针值用于初始化指针对象ptr。(确实应该是const char *ptr,但这是一个附带问题。)

函数类型的表达式(例如函数名)被隐式转换为指向函数的指针,除非它是:

  • 一元运算&符的操作数;或者
  • sizeof(的操作数sizeof function_name是非法的,不是指针的大小)。

而已。

在这两种情况下,都不会创建指针对象。该表达式被转换为(“衰减”为)指针值,也称为地址。

(这两种情况下的“转换”都不是普通的类型转换,如强制转换运算符指定的类型转换。它不采用操作数的值并使用它来计算结果的值,就像发生在int转换float。而是在编译时将数组或函数类型的表达式“转换”为指针类型的表达式。在我看来,“调整”这个词会比“转换”更清楚。)

请注意,数组索引运算符[]和函数调用“运算符”()都需要一个指针。在像 的普通函数调用func(42)中,函数名称func“衰减”为指向函数的指针,然后在调用中使用该指针。(这种转换实际上不需要在生成的代码中执行,只要函数调用做正确的事情。)

函数规则有一些奇怪的结果。在大多数情况下,表达式func被转换为指向函数的指针func。In &func,func不转换为指针,而是&产生函数的地址,即指针值。In *func,func被隐式转换为指针,然后*取消引用它以产生函数本身,然后(在大多数情况下)将其转换为指针。在****func中,这种情况反复发生。

(C11 标准的草案说数组还有一个例外,即当数组是 new_Alignof运算符的操作数时。这是草案中的错误,在最终发布的 C11 标准中更正;_Alignof只能应用于带括号的键入名称,而不是表达式。)

数组的地址及其第一个成员的地址:

int arr[10];
&arr;    /* address of entire array */
&arr[0]; /* address of first element */

是相同的内存地址,但它们的类型不同。前者是整个数组对象的地址,是类型int(*)[10](指向10s数组的指针int);后者是类型int*。这两种类型不兼容(例如,您不能合法int*地为int(*)[10]对象赋值),并且指针运算在它们上的行为不同。

有一条单独的规则表示声明的数组或函数类型的函数参数在编译时调整(未转换)为指针参数。例如:

void func(int arr[]);

完全等同于

void func(int *arr);

这些规则(数组表达式的转换和数组参数的调整)结合起来,对 C 中数组和指针之间的关系造成了很大的混乱。

comp.lang.c FAQ的第 6 节很好地解释了细节。

这方面的权威来源是 ISO C 标准。 N1570 (1.6 MB PDF) 是 2011 标准的最新草案;这些转换在第 6.3.2.1 节第 3 段(数组)和第 4 段(函数)中指定。该草案错误地引用了_Alignof,这实际上并不适用。

顺便说一句,printf您示例中的调用完全不正确:

int fruits[10];
printf("Address IN constant pointer is %p\n",fruits);
printf("Address OF constant pointer is %p\n",&fruits); 

%p格式需要一个类型为 的参数void*。如果类型指针int*int(*)[10]具有相同的表示形式void*和 作为参数以相同的方式传递,就像大多数实现的情况一样,它可能会起作用,但不能保证。您应该将指针显式转换为void*

int fruits[10];
printf("Address IN constant pointer is %p\n", (void*)fruits);
printf("Address OF constant pointer is %p\n", (void*)&fruits);

那么为什么要这样做呢?问题在于数组在某种意义上是 C 语言中的二等公民。您不能在函数调用中按值传递数组作为参数,也不能将其作为函数结果返回。要使数组有用,您需要能够对不同长度的数组进行操作。单独strlen的函数 for char[1]、 for char[2]、 forchar[3]等等(所有这些都是不同的类型)将是不可能的笨拙。因此,数组是通过指向其元素的指针来访问和操作的,而指针算法提供了一种遍历这些元素的方法。

如果数组表达式没有衰减为指针(在大多数情况下),那么您对结果将无能为力。C 源自早期的语言(BCPL 和 B),它们甚至不一定区分数组和指针。

其他语言能够将数组作为一等类型处理,但这样做需要额外的功能,这些功能不会“本着 C 的精神”,C 仍然是一种相对低级的语言。

我不太确定以这种方式处理函数的基本原理。确实没有函数类型的,但是该语言可能需要一个函数(而不是指向函数的指针)作为函数调用中的前缀,*间接调用需要一个显式运算符:(*funcptr)(arg)。能够省略*是一种便利,但不是一个巨大的便利。这可能是历史惯性和数组处理一致性的结合。

于 2013-07-06T19:17:10.690 回答
2

您问题第一部分的链接页面中给出的描述肯定是完全不正确的。那里没有指针,无论是否恒定。您可以在@KeithThompson 的回答中找到对数组/函数行为的详尽解释。

最重要的是,添加(作为旁注)作为两部分对象实现的数组 - 指向独立无名内存块的命名指针 - 并不完全是空想的,这可能是有意义的。它们以特定的形式存在于 C 语言的前身——B 语言中。最初,它们从 B 转移到 C 完全没有改变。您可以在 Dennis Ritchie 的“ The Development of the C Language ”文档中阅读有关它的内容(请参阅“Embryonic C”部分)。

但是,正如该文档中所述,这种数组实现与 C 语言的一些新特性不兼容,例如结构类型。在 struct 对象中包含两部分数组会将这些对象转换为具有非平凡构造的更高级别的实体。它还会使它们与原始内存操作(诸如此类memcpy)不兼容。这些考虑是数组从两部分对象重新设计为当前的单部分形式的原因。而且,正如您在该文档中看到的那样,重新设计是在考虑 B 样式数组的向后兼容性的情况下执行的。

因此,首先,这就是为什么许多人对 C 样式数组的行为感到困惑的原因,认为其中某处隐藏了一个指针。现代 C 数组的行为是专门为模仿/维持这种错觉而设计的。其次,一些古老的文档可能仍然包含那个“胚胎”时代的遗留物(尽管看起来你链接的文档不应该是其中之一。)

于 2013-07-06T20:27:05.727 回答
2

简短的回答是肯定的……除了有时。通常在声明数组后,每次使用它的名称时,都会将其转换为指向数组对象第一个元素的指针。但是,在某些情况下不会发生这种情况。这些没有发生这种情况的情况可以在@KeithThompson 的回答中找到

与您的数组类似,函数类型也将转换为指针值......有时除外。可以再次在@KeithThompson 的回答中找到不再发生这种情况的情况。在这里

于 2013-07-06T19:15:11.843 回答
2

有一个更好的方法来考虑它。数组类型的表达式(包括:数组名称、指向数组的指针的取消引用、二维数组的下标等)就是这样——数组类型的表达式。它不是指针类型的表达式。但是,如果在需要指针的上下文中使用该语言,它会提供从数组类型表达式到指针类型表达式的隐式转换。

你不需要记住,哦,它被转换为指针 "except" sizeof&等等。你只需要考虑表达式的上下文。

例如,考虑当您尝试将数组表达式传递给函数调用时。根据 C 标准,函数参数不能是数组类型。如果对应的参数是指针类型(它必须是为了编译),那么编译器会看到,哦,它想要一个指针,所以它应用数组表达式到指针类型的转换。

或者,如果您使用带有解引用运算符*、算术运算符+ -或下标运算符的数组表达式[],这些运算符都对指针进行操作,因此编译器再次看到并应用转换。

当您尝试分配数组表达式时,在 C 中,数组类型是不可分配的,因此它可以编译的唯一方法是将其分配给指针类型,在这种情况下,编译器再次看到它需要一个指针, 并应用转换。

当您将它与sizeof, 和一起使用时&,这些上下文对数组来说是有意义的,因此编译器不会费心应用转换。这些被视为数组到指针转换的“例外”的唯一原因是, C 中的所有其他表达式上下文(如您在上面的示例中所见)对于数组类型(数组类型在 C 中是如此残缺),而这几个是唯一“剩下的”。

于 2013-07-07T19:42:13.840 回答