数组类型的表达式被隐式转换为指向数组对象第一个元素的指针除非它是:
- 一元
&运算符的操作数;
-
sizeof 的操作数;或
- 用于初始化数组对象的初始化程序中的字符串文字。
第三种情况的一个例子是:
char arr[6] = "hello";
"hello" 是char[6] 类型的数组表达式(5 加1 表示'\0' 终止符)。它没有转换为地址; "hello" 的完整 6 字节值被复制到数组对象 arr 中。
另一方面,在这个:
char *ptr = "hello";
数组表达式"hello"“衰减”为指向'h'的指针,该指针值用于初始化指针对象ptr。 (确实应该是const char *ptr,但这是一个附带问题。)
函数类型的表达式(例如函数名)被隐式转换为指向函数的指针除非它是:
- 一元
&运算符的操作数;或
-
sizeof 的操作数(sizeof function_name 是非法的,不是指针的大小)。
就是这样。
在这两种情况下,都没有创建指针object。表达式被转换为(“衰减”为)指针值,也称为地址。
(这两种情况下的“转换”都不是普通的类型转换,就像由强制转换运算符指定的那样。它不采用操作数的值并使用它来计算结果的值,如int-to-float 转换会发生。而是数组或函数类型的 表达式 在编译时“转换”为指针类型的 表达式 . 在我看来,“调整”这个词会比“转换”更清楚。)
请注意,数组索引运算符[] 和函数调用“运算符”() 都需要一个指针。在像func(42) 这样的普通函数调用中,函数名称func“衰减”为指向函数的指针,然后在调用中使用该指针。 (这种转换实际上不需要在生成的代码中执行,只要函数调用做正确的事情。)
函数规则有一些奇怪的结果。在大多数情况下,表达式func 被转换为指向函数func 的指针。在&func 中,func 不会转换为指针,但& 会产生函数的地址,即指针值。在*func 中,func 被隐式转换为指针,然后* 取消引用它以产生函数本身,然后(在大多数情况下)转换为指针。在****func 中,这种情况反复发生。
(C11 标准的草案说数组还有一个例外,即当数组是新 _Alignof 运算符的操作数时。这是草案中的错误,在最终发布的 C11 标准中更正;@ 987654355@ 只能应用于带括号的类型名称,不能应用于表达式。)
数组的地址及其第一个成员的地址:
int arr[10];
&arr; /* address of entire array */
&arr[0]; /* address of first element */
是相同的内存地址,但它们属于不同的类型。前者是整个数组对象的地址,类型为int(*)[10](指向10个数组的指针ints);后者是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 函数分别用于char[1]、char[2]、char[3] 等等(所有这些都是不同的类型)将非常笨拙。因此,数组是通过指向其元素的指针来访问和操作的,而指针算法提供了一种遍历这些元素的方法。
如果一个数组表达式没有衰减到一个指针(在大多数情况下),那么你对结果就无能为力了。而且 C 源自早期的语言(BCPL 和 B),它们甚至不一定区分数组和指针。
其他语言能够将数组作为一等类型来处理,但这样做需要额外的功能,这些功能不符合“C 的精神”,C 仍然是一种相对低级的语言。
我不太确定以这种方式处理函数的基本原理。确实没有函数类型的值,但是该语言可能需要一个函数(而不是指向函数的指针)作为函数调用的前缀,需要显式的*间接呼叫的操作员:(*funcptr)(arg)。能够省略* 是一种方便,但不是很方便。可能是历史惯性和对数组处理的一致性的结合。