数组类型的表达式被隐式转换为指向数组对象第一个元素的指针,除非它是:
- 一元运算
&
符的操作数;
- 的操作数
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)
。能够省略*
是一种便利,但不是一个巨大的便利。这可能是历史惯性和数组处理一致性的结合。