何时数组名称或函数名称“转换”为指针? (在C中)
1) 误解 :
-
每当使用C语言声明数组时,都会隐式创建指向数组第一个元素的指针(数组的名称)。 (是吗?我不这么认为!)
-
这个页面的前两行(虽然我不确定信息的正确性)说明相同。
正如我们所看到的,当我们声明一个数组时,为数组的单元分配一个连续的内存块,并且还分配一个指针单元(适当类型)并初始化为指向数组的第一个单元。
-
但是当我输出该指针中包含的地址和该指针的地址时 ,它们会变成相同的。 所以,我认为毕竟不会创建指针。
2)我从这个问题中选了这个 。
- 在大多数情况下,数组名称将转换为指针。
任何人都可以详细解释编译器何时决定将数组名转换为指针, 为什么 ?
PS:请解释一下function 。 同样在这个链接中,给出了一个例子,说对于函数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
数组类型的表达式被隐式转换为指向数组对象的第一个元素的指针, 除非它是:
- 一元
&
运算符的操作数; -
sizeof
的操作数; 要么 - 初始值设定项中的字符串文字,用于初始化数组对象。
第三种情况的例子是:
char arr[6] = "hello";
"hello"
是char[6]
类型的数组表达式(对于'\0'
终止符,5加1)。 它没有转换为地址; "hello"
的完整6字节值被复制到数组对象arr
。
另一方面,在这:
char *ptr = "hello";
数组表达式"hello"
“衰减”到指向'h'
指针,该指针值用于初始化指针对象ptr
。 (它应该是const char *ptr
,但这是一个副问题。)
函数类型的表达式(例如函数名)被隐式转换为指向函数的指针, 除非它是:
- 一元
&
运算符的操作数; 要么 -
sizeof
的操作数(sizeof function_name
是非法的,不是指针的大小)。
而已。
在这两种情况下,都不会创建指针对象 。 表达式转换为(“衰减”)指针值,也称为地址。
请注意,数组索引operator []
和函数调用“operator” ()
都需要指针。 在像func(42)
这样的普通函数调用中,函数名称func
“衰减”到指向函数的指针,然后在调用中使用它。 (这种转换实际上不需要在生成的代码中执行,只要函数调用做正确的事情。)
function规则有一些奇怪的后果。 在大多数情况下,表达式func
被转换为指向函数func
的指针。 在&func
, func
不会转换为指针,但是&
产生函数的地址,即指针值。 在*func
, func
被隐式转换为指针,然后*
取消引用它以产生函数本身,然后(在大多数情况下)转换为指针。 在****func
,这种情况反复发生。
(C11标准的草案说数组有另一个例外,即当数组是新_Alignof
运算符的操作数时。这是草案中的错误,在最终发布的C11标准中得到纠正; _Alignof
只能应用于带括号的类型名称,而不是表达式。)
数组的地址和第一个成员的地址:
int arr[10]; &arr; /* address of entire array */ &arr[0]; /* address of first element */
是相同的内存地址,但它们是不同的类型。 前者是整个数组对象的地址,类型为int(*)[10]
(指向10个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*
相同的表示forms,并以相同的方式作为参数传递,就像大多数实现的情况一样,它可能会起作用,但不能保证。 您应该将指针显式转换为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语言中的二等公民。您不能通过值将数组作为函数调用中的参数传递,并且您不能将其作为函数结果返回。 要使数组有用,您需要能够在不同长度的数组上运行。 char[1]
, char[2]
, char[3]
等单独的strlen
函数,等等(所有这些都是不同的类型)将是不可能的笨拙。 因此,通过指向其元素的指针来访问和操作数组,而指针算法提供了遍历这些元素的方法。
如果数组表达式没有衰减到指针(在大多数情况下),那么结果就没那么多了。 C来自早期语言(BCPL和B),它们甚至不一定能区分数组和指针。
其他语言能够将数组作为一流类型处理,但这样做需要额外的function,这些function不会“本着C语言”,这仍然是一种相对低级的语言。
我不太确定以这种方式处理函数的理由。 确实没有函数类型的值 ,但是语言可能需要一个函数(而不是指向函数的指针)作为函数调用中的前缀,需要一个显式的*
运算符来进行间接调用: (*funcptr)(arg)
。 能够省略*
是一种便利,但不是一个巨大的便利。 它可能是历史惯性和与arrays处理的一致性的组合。
您问题第一部分链接页面中给出的描述肯定是完全错误的。 那里没有指针,不管是不是。 您可以在@ KeithThompson的答案中找到有关数组/函数行为的详尽解释。
最重要的是,添加(作为旁注)可能有意义的是,实现为两部分对象的数组 – 指向独立无名内存块的命名指针 – 并不完全是空洞的。 它们以C语言的前身–B语言的特定forms存在。 最初它们从B到C完全没有变化。 您可以在Dennis Ritchie的“ C语言的发展 ”文档中阅读它(参见“胚胎C”部分)。
但是,正如在该文档中所述,这种数组实现与C语言的一些新特性(如结构类型)不兼容。 在struct对象中包含两部分数组会将这些对象转换为具有非平凡构造的更高级别的实体。 它还会使它们与原始内存操作(如memcpy
等)不兼容。 这些考虑因素是将数组从两部分对象重新设计为其当前单部分forms的原因。 而且,正如您可以在该文档中阅读的那样,重新设计是在考虑向后兼容B风格arrays的情况下执行的。
所以,首先,这就是为什么许多人对C风格数组的行为感到困惑,认为某处隐藏着一个指针。 现代Carrays的行为专门用于模拟/维持这种错觉。 其次,一些古老的文件可能仍然包含来自“胚胎”时代的剩余物(尽管,它看起来不像您链接的文件应该是其中之一。)
简短的回答是肯定的……除了有时候。 通常在声明数组之后,每次使用其名称时,它都会转换为指向数组对象的第一个元素的指针。 但是在某些情况下不会发生这种情况。 这些不会发生的情况可以在@ KeithThompson的答案中找到 。
与数组类似,函数类型也将转换为指针值…有时除外。 再次发生这种情况的情况可以在@ KeithThompson的答案中找到。 在这里 。
有一个更好的方式来考虑它。 数组类型的表达式(包括:数组名称,指向数组的指针的解引用,二维数组的下标等)就是 – 数组类型的表达式。 它不是指针类型的表达式。 但是,如果语言在需要指针的上下文中使用 , 则该语言提供从数组类型的表达式到指针类型的表达式的隐式转换。
你不需要记住它,哦,它被转换为指针“除了” sizeof
和&
等等。你只需要考虑表达式的上下文。
例如,考虑何时尝试将数组表达式传递给函数调用。 根据C标准,函数参数不能是数组类型。 如果相应的参数是指针类型(它必须是为了编译),那么编译器会看到,哦,它想要一个指针,所以它应用了array-expression-to-pointer-type转换。
或者,如果使用带解除引用运算符*
的数组表达式,或算术运算符+
-
或下标运算符, []
; 这些运算符都在指针上运行,因此编译器再次看到它并应用转换。
当您尝试分配数组表达式时,在C中,数组类型是不可分配的,因此它可以编译的唯一方法是将其分配给指针类型,在这种情况下,编译器再次看到它需要一个指针,并应用转换。
当您将它与sizeof
和&
一起使用时,这些上下文对于数组本身就有意义,因此编译器不会费心应用转换。 这些被视为数组到指针转换的“exception”的唯一原因就是,C中的所有其他表达式上下文 (如上例中所示)对数组类型没有意义(数组) C)中的类型是如此残缺,而这些只是“左”的唯一。