简体   繁体   English

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

[英]When is an array name or a function name 'converted' into a pointer ? (in C)

1) Misconception : 1)误解

  • Whenever an array is declared in C language, a pointer to the first element of the array is created (the name of the array) implicitly.每当在 C 语言中声明数组时,都会隐式创建指向数组第一个元素的指针(数组的名称)。 (Is it? I don't think so!) (是吗?我不这么认为!)

  • The first two lines of this page (though I am not sure about the correctness of the information) state the same. 页面的前两行(尽管我不确定信息的正确性)表示相同。

    As we have seen, when we declare an array, a contiguous block of memory is allocated for the cells of the array and a pointer cell (of the appropriate type) is also allocated and initialized to point to the first cell of the array.正如我们所见,当我们声明一个数组时,会为数组的单元格分配一个连续的内存块,并且还会分配并初始化一个指针单元格(适当类型的)以指向数组的第一个单元格。

  • But when I output the address contained in that pointer and the address of that pointer, they turn out to be the same.但是当我输出包含该指针中的地址该指针的地址,它们结果是相同的。 So, I think a pointer is not created after all.所以,我认为毕竟没有创建指针。

2) I picked this up from this question. 2)我从这个问题中学到了这一点

  • In most cases array names are converted to pointers.在大多数情况下,数组名称被转换为指针。

Can anyone give a detailed explanation of WHEN the compiler decides to convert an array name into a pointer, and WHY ?任何人都可以详细解释编译器何时决定数组名称转换为指针,以及为什么

PS: Please explain the same with functions . PS:请解释相同的功能 Also in this link, an example was given, saying that for a function int square(int,int) , any of the square , &square , *square , **square refers to the same function pointer.同样在这个链接中,给出了一个例子,说对于函数int square(int,int) ,任何square&square*square**square指的是同一个函数指针。 Can you explain?你可以解释吗?

Edit : Code snippet编辑:代码片段

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

Output :输出 :

Address IN constant pointer is 0xbff99ca8
Address OF constant pointer is 0xbff99ca8

An expression of array type is implicitly converted to a pointer to the array object's first element unless it is:数组类型的表达式被隐式转换为指向数组对象的第一个元素的指针,除非它是:

  • The operand of the unary & operator;一元&运算符的操作数;
  • The operand of sizeof ; sizeof的操作数; or要么
  • A string literal in an initializer used to initialize an array object.用于初始化数组对象的初始化程序中的字符串文字。

An examples of the third case are:第三种情况的一个例子是:

char arr[6] = "hello";

"hello" is an array expression, of type char[6] (5 plus 1 for the '\\0' terminator). "hello"是一个数组表达式,类型为char[6] (5 加 1 表示'\\0'终止符)。 It's not converted to an address;它不会转换为地址; the full 6-byte value of of "hello" is copied into the array object arr . "hello"的完整 6 字节值被复制到数组对象arr

On the other hand, in this:另一方面,在这方面:

char *ptr = "hello";

the array expression "hello" "decays" to a pointer to the 'h' , and that pointer value is used to initialize the pointer object ptr .数组表达式"hello" "decays" 指向指向'h'指针,该指针值用于初始化指针对象ptr (It should really be const char *ptr , but that's a side issue.) (它真的应该是const char *ptr ,但这是一个附带问题。)

An expression of function type (such as a function name) is implicitly converted to a pointer to the function unless it is:函数类型的表达式(例如函数名)被隐式转换为指向该函数的指针,除非它是:

  • The operand of the unary & operator;一元&运算符的操作数; or要么
  • The operand of sizeof ( sizeof function_name is illegal, not the size of a pointer). sizeof的操作数( sizeof function_name是非法的,不是指针的大小)。

That's it.而已。

In both cases, no pointer object is created.在这两种情况下,都不会创建指针对象 The expression is converted to ("decays" to) a pointer value, also known as an address.该表达式被转换为(“衰减”为)一个指针值,也称为地址。

(The "conversion" in both these cases isn't an ordinary type conversion like the one specified by a cast operator. It doesn't take the value of an operand and use it to compute the value of the result, as would happen for an int -to- float conversion. Rather an expression of array or function type is "converted" at compile time to an expression of pointer type. In my opinion the word "adjusted" would have been clearer than "converted".) (这两种情况下的“转换”不是像强制转换运算符所指定的那样的普通类型转换。它不获取操作数的值并使用它来计算结果的值,就像一个int -到- float转换,而是阵列或功能类型的表达被“转化”在编译时指针类型的表达式。在我的“调整”会比“转化”更清晰的意见的话。)

Note that both the array indexing operator [] and the function call "operator" () require a pointer.请注意,数组索引运算符[]和函数调用“operator” ()都需要一个指针。 In an ordinary function call like func(42) , the function name func "decays" to a pointer-to-function, which is then used in the call.在像func(42)这样的普通函数调用中,函数名称func “衰减”为指向函数的指针,然后在调用中使用该指针。 (This conversion needn't actually be performed in the generated code, as long as the function call does the right thing.) (这种转换实际上不需要在生成的代码中执行,只要函数调用正确。)

The rule for functions has some odd consequences.函数规则有一些奇怪的后果。 The expression func is, in most contexts, converted to a pointer to the function func .在大多数情况下,表达式func被转换为指向函数func的指针。 In &func , func is not converted to a pointer, but & yields the function's address, ie, a pointer value.&funcfunc不会转换为指针,而是&产生函数的地址,即指针值。 In *func , func is implicitly converted to a pointer, then * dereferences it to yield the function itself, which is then (in most contexts) converted to a pointer.*funcfunc被隐式转换为指针,然后*取消引用它以产生函数本身,然后(在大多数情况下)转换为指针。 In ****func , this happens repeatedly.****func ,这种情况反复发生。

(A draft of the C11 standard says that there's another exception for arrays, namely when the array is the operand of the new _Alignof operator. This is an error in the draft, corrected in the final published C11 standard; _Alignof can only be applied to a parenthesized type name, not to an expression.) (C11 标准草案说数组还有另一个例外,即当数组是新的_Alignof运算符的操作数时。这是草案中的一个错误,在最终发布的 C11 标准中已更正; _Alignof只能应用于括号中的类型名称,而不是表达式。)

The address of an array and the address of its first member:数组的地址及其第一个成员的地址:

int arr[10];
&arr;    /* address of entire array */
&arr[0]; /* address of first element */

are the same memory address, but they're of different types.是相同的内存地址,但它们的类型不同。 The former is the address of the entire array object, and is of type int(*)[10] (pointer to array of 10 int s);前者是整个数组对象的地址,类型为int(*)[10] (指向 10 个int数组的指针); the latter is of type int* .后者是int*类型。 The two types are not compatible (you can't legally assign an int* value to an int(*)[10] object, for example), and pointer arithmetic behaves differently on them.这两种类型不兼容(例如,您不能合法地将int*值分配给int(*)[10]对象),并且指针算术对它们的行为不同。

There's a separate rule that says that a declared function parameter of array or function type is adjusted at compile time (not converted) to a pointer parameter.有一个单独的规则,即阵列或功能类型的声明的函数参数是在编译时(未转化的)的指针参数调整 For example:例如:

void func(int arr[]);

is exactly equivalent to完全等同于

void func(int *arr);

These rules (conversion of array expressions and adjustment of array parameters) combine to create a great deal of confusion regarding the relationship between arrays and pointers in C.这些规则(数组表达式的转换和数组参数的调整)结合起来,对 C 中数组和指针之间的关系造成了很大的混淆。

Section 6 of the comp.lang.c FAQ does an excellent job of explaining the details. comp.lang.c FAQ 的第 6 节很好地解释了细节。

The definitive source for this is the ISO C standard.这方面的权威来源是 ISO C 标准。 N1570 (1.6 MB PDF) is the latest draft of the 2011 standard; N1570 (1.6 MB PDF) 是 2011 标准的最新草案; these conversions are specified in section 6.3.2.1, paragraphs 3 (arrays) and 4 (functions).这些转换在第 6.3.2.1 节的第 3(数组)和第 4(函数)段中指定。 That draft has the erroneous reference to _Alignof , which doesn't actually apply.该草案错误地引用了_Alignof ,实际上并不适用。

Incidentally, the printf calls in your example are strictly incorrect:顺便说一句,您示例中的printf调用是完全不正确的:

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

The %p format requires an argument of type void* . %p格式需要一个void*类型的参数。 If pointers of type int* and int(*)[10] have the same representation as void* and are passed as arguments in the same way, as is the case for most implementations, it's likely to work, but it's not guaranteed.如果类型的指针int*int(*)[10]有相同的表示为void*和以同样的方式传递参数,如对于大多数实现的情况下,它可能的工作,但它不能保证。 You should explicitly convert the pointers to 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);

So why is it done this way?那么为什么要这样做呢? The problem is that arrays are in a sense second-class citizens in C. You can't pass an array by value as an argument in a function call, and you can't return it as a function result.问题是数组在某种意义上是 C 中的二等公民。您不能在函数调用中按值传递数组作为参数,也不能将它作为函数结果返回。 For arrays to be useful, you need to be able to operate on arrays of different lengths.要使数组有用,您需要能够对不同长度的数组进行操作。 Separate strlen functions for char[1] , for char[2] , for char[3] , and so forth (all of which are distinct types) would be impossibly unwieldy.单独的用于char[1]char[2]char[3]等的strlen函数(所有这些都是不同的类型)将是不可能的。 So instead arrays are accessed and manipulated via pointers to their elements, with pointer arithmetic providing a way to traverse those elements.因此,数组是通过指向其元素的指针来访问和操作的,指针算法提供了一种遍历这些元素的方法。

If an array expression didn't decay to a pointer (in most contexts), then there wouldn't be much you could do with the result.如果数组表达式没有衰减为指针(在大多数情况下),那么您对结果无能为力。 And C was derived from earlier languages (BCPL and B) that didn't necessarily even distinguish between arrays and pointers. C 源自早期的语言(BCPL 和 B),它们甚至不一定区分数组和指针。

Other languages are able to deal with arrays as first-class types but doing so requires extra features that wouldn't be "in the spirit of C", which continues to be a relatively low-level language.其他语言能够将数组作为一流类型处理,但这样做需要额外的功能,这些功能不会“符合 C 的精神”,而 C 仍然是一种相对低级的语言。

I'm less sure about the rationale for treating functions this way.我不太确定以这种方式处理函数的理由。 It's true that there are no values of function type, but the language could have required a function (rather than a pointer-to-function) as the prefix in a function call, requiring an explicit * operator for an indirect call: (*funcptr)(arg) .确实没有函数类型的,但该语言可能需要一个函数(而不是函数指针)作为函数调用的前缀,需要一个显式的*操作符来进行间接调用: (*funcptr)(arg) Being able to omit the * is a convenience, but not a tremendous one.能够省略*是一种方便,但不是很大。 It's probably a combination of historical inertia and consistency with the treatment of arrays.这可能是历史惯性和对数组处理的一致性的结合。

The short answer is yes...except for sometimes.简短的回答是肯定的......除了有时。 Usually after an array is declared, each time its name is used, it is converted to a pointer to the array object's first element.通常在声明数组后,每次使用其名称时,都会将其转换为指向数组对象第一个元素的指针。 However there are some cases where this does not occur.但是,在某些情况下不会发生这种情况。 These cases where this does not occur can be found in @KeithThompson's answer here .这些不会发生的情况可以在 @KeithThompson's answer here 中找到

Similarly to your array, a function type will also be converted to a pointer value...except for sometimes.与您的数组类似,函数类型也将转换为指针值......除了有时。 The cases where this does not occur again can be found at @KeithThompson's answer again.再次出现这种情况的情况可以在@KeithThompson 的回答中再次找到。 here . 在这里

The description given in the linked page in the first part of your question is certainly completely incorrect.您问题第一部分的链接页面中给出的描述肯定是完全不正确的。 There is no pointer there, constant or not.那里没有指针,无论是否恒定。 You can find the exhaustive explanation of array/function behavior in @KeithThompson's answer.您可以在@KeithThompson 的回答中找到对数组/函数行为的详尽解释。

On top of that it might make sense to add (as a side note) that arrays implemented as two-part objects - a named pointer pointing to an independent nameless block of memory - are not exactly chimerical.最重要的是,添加(作为旁注)作为两部分对象实现的数组 - 一个指向独立无名内存块的命名指针 - 并不完全是幻想。 They existed in that specific form in the predecessor of C language - B language.它们以那种特定的形式存在于 C 语言的前身——B 语言中。 And initially they were carried over from B to C completely unchanged.最初它们从 B 转移到 C 完全没有改变。 You can read about it in Dennis Ritchie's " The Development of the C Language " document (see the "Embryonic C" section).您可以在 Dennis Ritchie 的“ The Development of the C Language ”文档中阅读到它(参见“Embryonic C”部分)。

However, as it is stated in that very document, this kind of array implementation was incompatible with some new features of C language, like struct types.但是,正如该文档中所述,这种数组实现与 C 语言的一些新特性(如结构类型)不兼容。 Having two-part arrays inside struct objects would turn such objects into higher-level entities with non-trivial construction.在 struct 对象中拥有由两部分组成的数组会将这些对象转换为具有非平凡构造的更高级别的实体。 It would also make them incompatible with raw-memory operations (like memcpy and so on).这也会使它们与原始内存操作(如memcpy等)不兼容。 Such considerations are the reason arrays were redesigned from two-part objects into their current single-part form.这些考虑是将数组从两部分对象重新设计为当前的单部分形式的原因。 And, as you can read in that document, the redesign was performed with backward-compatibility with B-style arrays in mind.而且,正如您在该文档中所读到的,重新设计是在考虑到 B 样式数组的向后兼容性的情况下进行的。

So, firstly, this is why many people get confused by the behavior of C-style arrays, believing that there a pointer hidden in there somewhere.所以,首先,这就是为什么许多人对 C 风格数组的行为感到困惑,认为那里隐藏着一个指针。 The behavior of the modern C array was specifically designed to emulate/sustain that illusion.现代 C 数组的行为是专门为模仿/维持这种错觉而设计的。 And, secondly, some archaic document might still contain leftovers from that "embryonic" era (although, it does not look like the document you linked should be one of them.)其次,一些古老的文档可能仍然包含那个“胚胎”时代的遗留物(尽管看起来你链接的文档不应该是其中之一。)

There is a much better way to think about it.有一种更好的方式来思考它。 An expression of array type (which includes: an array name, a dereference of a pointer to an array, a subscripting of a two-dimensional array, etc.) is just that -- an expression of array type.数组类型的表达式(包括:数组名、数组指针的解引用、二维数组的下标等)就是——数组类型的表达式。 It is not an expression of pointer type.它不是指针类型的表达式。 However, the language provides an implicit conversion from an expression of array type to an expression of pointer type, if it's used in a context that wants a pointer .但是,如果在需要指针的上下文中使用该语言,则该语言提供了从数组类型表达式到指针类型表达式的隐式转换。

You don't need to remember that, oh, it gets converted to a pointer "except" sizeof , and & , etc. You just have to think about the context of the expression.你不需要记住,哦,它被转换为一个指针“除了” sizeof&等。你只需要考虑表达式的上下文。

For example, consider when you try to pass an array expression to a function call.例如,考虑何时尝试将数组表达式传递给函数调用。 Function parameters cannot be of array type per the C standard.根据 C 标准,函数参数不能是数组类型。 If the corresponding parameter is a pointer type (which it must be in order to compile), then the compiler sees that, oh, it wants a pointer, so it applies the array-expression-to-pointer-type conversion.如果对应的参数是指针类型(为了编译必须是指针类型),那么编译器看到,哦,它想要一个指针,所以它应用数组表达式到指针类型的转换。

Or, if you use an array expression with the dereference operator * , or arithmetic operators + - , or subscript operator, [] ;或者,如果您使用带有解引用运算符*或算术运算符+ -或下标运算符[]的数组表达式; these operators all operate on pointers, so again, the compiler sees that, and applies the conversion.这些运算符都对指针进行操作,因此编译器再次看到并应用转换。

When you try to assign an array expression, well, in C, array types are not assignable, so the only way it could compile is if it's assigned to a pointer type, in which case, again, the compiler sees that it needs a pointer, and applies the conversion.当您尝试分配数组表达式时,好吧,在 C 中,数组类型是不可分配的,因此它可以编译的唯一方法是将其分配给指针类型,在这种情况下,编译器再次看到它需要一个指针,并应用转换。

When you use it with sizeof , and & , those contexts make sense inherently for arrays, so the compiler doesn't bother to apply the conversion.当您将它与sizeof&一起使用时,这些上下文对数组具有固有的意义,因此编译器不会费心应用转换。 The only reason that these are viewed as the "exception" to the array-to-pointer conversion, is simply that, all other expression contexts (as you can see in the examples above) in C do not make sense for array types (array types are so crippled in C), and these few are the only ones "left".这些被视为数组到指针转换的“例外”的唯一原因很简单,C 中的所有其他表达式上下文(如您在上面的示例中所见)对于数组类型(数组类型在 C 中如此残缺),而这些是唯一“留下”的类型。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM