何时数组名称或函数名称“转换”为指针? (在C中)

时间:2013-07-06 19:04:36

标签: c arrays function pointers function-pointers

1) 误解

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

  • this页面的前两行(虽然我不确定信息的正确性)也说明了相同的内容。

      

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

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

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

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

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

PS:请用函数解释一下。同样在this链接中,给出了一个示例,说明对于函数int square(int,int),任何square&square*square,{{1} }指的是同一个函数指针。你可以解释吗?

修改:代码段

**square

输出:

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

4 个答案:

答案 0 :(得分:33)

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

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

第三种情况的例子是:

char arr[6] = "hello";

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

另一方面,在此:

char *ptr = "hello";

数组表达式"hello""衰变"指向'h'的指针,该指针值用于初始化指针对象ptr。 (它应该是const char *ptr,但这是一个副作用。)

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

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

那就是它。

在这两种情况下,都没有创建指针 object 。表达式转换为("衰减")指针值,也称为地址。

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

功能规则有一些奇怪的后果。在大多数情况下,表达式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 s的数组的指针);后者的类型为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语言中的二等公民。您不能通过值将数组作为函数调用中的参数传递,并且您不能将其作为函数结果返回。要使数组有用,您需要能够对不同长度的数组进行操作。对strlenchar[1]char[2]char[3]分别*函数,等等(所有这些都是不同的类型)将是不可能的笨拙。因此,数组通过指向其元素的指针进行访问和操作,指针算法提供了遍历这些元素的方法。

如果数组表达式没有衰减到指针(在大多数情况下),那么你对结果的处理就不会那么多了。 C来自早期语言(BCPL和B),它们甚至不必区分数组和指针。

其他语言能够将数组作为一流类型处理,但这样做需要额外的功能,而这些功能本身并不是C"它仍然是一个相对较低的 - 等级语言。

我不太确定以这种方式治疗功能的理由。确实没有函数类型的,但是语言可能需要一个函数(而不是指向函数的指针)作为函数调用中的前缀,需要一个间接呼叫的显式(*funcptr)(arg)运算符:*。能够省略{{1}}是一种便利,但不是一种巨大的便利。它可能是历史惯性和与阵列处理的一致性的组合。

答案 1 :(得分:2)

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

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

答案 2 :(得分:2)

问题第一部分链接页面中的说明肯定是完全错误的。那里没有指针,不管是不是。您可以在@ KeithThompson的答案中找到有关数组/函数行为的详尽解释。

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

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

所以,首先,这就是为什么许多人对C风格数组的行为感到困惑,认为某处隐藏着一个指针。现代C阵列的行为专门用于模拟/维持这种错觉。其次,一些陈旧的文件可能仍然包含来自“胚胎”时代的剩余物(尽管,它看起来不像您链接的文件应该是其中之一。)

答案 3 :(得分:2)

有一种更好的方式来考虑它。数组类型的表达式(包括:数组名称,指向数组的指针的解引用,二维数组的下标等)就是 - 数组类型的表达式。它不是指针类型的表达式。但是,该语言提供了从数组类型的表达式到指针类型表达式的隐式转换,如果它在需要指针的上下文中使用,则为

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

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

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

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

当您将其与sizeof&一起使用时,这些上下文对数组本身就有意义,因此编译器无需应用转换。这些被视为数组到指针转换的“异常”的唯一原因就是,所有其他表达式上下文(正如您在上面的例子中看到的那样)在C中没有对数组类型有意义(数组类型在C中是如此残缺),而这些只是“左”的唯一类型。