【问题标题】:When is an array name or a function name 'converted' into a pointer ? (in C)何时将数组名称或函数名称“转换”为指针? (在 C 中)
【发布时间】:2013-07-04 13:48:32
【问题描述】:

1) 误解

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

  • this 页面的前两行(虽然我不确定信息的正确性)状态相同。

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

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

2) 我是从this 问题中找到的。

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

谁能详细解释一下何时编译器决定一个数组名转换为一个指针,以及为什么

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

编辑:代码 sn-p

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

【问题讨论】:

标签: c arrays function pointers function-pointers


【解决方案1】:

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

  • 一元&运算符的操作数;
  • 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)。能够省略* 是一种方便,但不是很方便。可能是历史惯性和对数组处理的一致性的结合。

【讨论】:

  • @KeithThompson 精湛的解释!非常感谢 :) 你能否为此提供一个小示例代码行 - “用于初始化数组对象的初始化程序中的字符串文字”?
  • @KeithThompson 为什么在关于函数的解释中强调“在大多数情况下”?有什么例外吗?
  • @Appy 例如 char s[] = "";
  • @Appy:有两种例外情况,其中函数名称未转换为指针。我已经列出了它们。我还添加了一个用于初始化数组对象的字符串文字示例。
  • 我不同意第三种情况很重要。数组初始化语句的右侧在语法上不是表达式。只允许特定的语法元素作为初始值设定项,即一对带有元素的大括号(也不是表达式),或者在字符数组的情况下,字符串文字(恰好是表达式中的有效表达式上下文,但这不相关)。
【解决方案2】:

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

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

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

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

【讨论】:

  • 感谢您提供非常有用的链接以及您的解释。它帮助很大。
【解决方案3】:

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

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

【讨论】:

  • 并非总是如此。例如,给定int arr[10];sizeof arr 产生数组的大小,而不是指针的大小。请参阅我的答案以获取完整的例外列表。
  • @Sabashan “答案是肯定的,总是这样”让我感到困惑。查看我的编辑:)
  • @Appy:“答案是肯定的,永远都是”是不正确的。
  • @KeithThompson 是的,我同意,我读了你的回答,我正在修复我的
  • 当数组对象被声明时,转换不会发生。当数组名称(或任何数组类型的表达式)在异常情况之外的上下文中使用时,就会发生这种情况。
【解决方案4】:

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

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

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

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

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

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

【讨论】:

    猜你喜欢
    • 2019-08-27
    • 2017-04-04
    • 1970-01-01
    • 2013-04-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多