总是这样,我的意思是,数组名称总是指向数组的第一个元素的指针。为什么呢?它是实现有点事物还是语言特征?
答案 0 :(得分:15)
数组名称本身不是指针,但在大多数上下文中衰减为指向数组第一个元素的指针。就是这样,因为语言就是这样定义的。
来自C11 6.3.2.1 Lvalues,数组和函数指示符,第3段:
除非是
sizeof
运算符的操作数,_Alignof
运算符或一元 {{1} } 运算符,或者是用于初始化数组的字符串文字,类型为“ type ”的表达式转换为类型为“指向类型<的指针的表达式” / em>“指向数组对象的初始元素,而不是左值。
您可以从Arrays and Pointers的comp.lang.c FAQ部分了解有关此主题的更多信息(以及涉及的微妙行为)。
编辑抛开:同样的行为发生在C ++中,尽管语言指定它有点不同。作为参考,我在这里有一个C ++ 11草案, 4.2数组到指针转换,第1段:
“
&
N
数组”或“T
未知范围数组”的左值或右值可以转换为“指向{{1的指针”的右值}}”。结果是指向数组的第一个元素的指针。
答案 1 :(得分:8)
可以找到此行为的历史原因here。
C源自一个名为B的早期语言(go figure)。 B是无类型语言,内存被视为“单元格”的线性数组,基本上是无符号整数。
在B中,当您声明N元素数组时,如
auto a[10];
为数组分配N个单元格,并留出另一个单元格来存储第一个元素的地址,该元素绑定到变量a
。与在C中一样,数组索引是通过指针算法完成的:
a[j] == *(a+j)
这很好用,直到Ritchie开始向C语言添加结构类型。他在文章中给出的例子是一个假设的文件系统条目,它是一个节点id后跟一个名字:
struct {
int inumber;
char name[14];
};
他希望struct类型的内容与磁盘上的数据相匹配;一个整数的2个字节,紧接着是14个字节的名称。没有把指针存放到数组的第一个元素的好地方。
所以他摆脱了它。他没有为指针设置存储空间,而是设计了语言,以便从数组表达式本身计算指针值。
顺便说一下,这就是为什么数组表达式不能作为赋值的目标;它与写3 = 4;
实际上是一回事 - 你试图将值赋给另一个值。
答案 2 :(得分:2)
Carl Norum已经就这个问题给出了语言律师的答案(并得到了我的支持),这里有实施细节答案:
对于计算机,内存中的任何对象只是一个字节范围,就内存处理而言,由第一个字节的地址和字节大小唯一标识。即使你的内存中有int
,它的地址也不过是第一个字节的地址。大小几乎总是隐式的:如果将指针传递给int
,编译器就知道它的大小,因为它知道该地址的字节将被解释为int
。结构也是如此:它们的地址是第一个字节的地址,它们的大小是隐含的。
现在,语言设计人员可以使用与结构一样的数组来实现类似的语义,但是他们没有充分的理由:与传递指针相比,复制效率比现在更低效,结构已经在大多数情况下使用指针传递,并且数组通常意味着很大。通过语言对它们强制施加价值语义是非常大的。
因此,通过指定数组的名称实际上等于指针,数组只是被迫成为内存对象。为了不打破数组与其他内存对象的相似性,大小再次被认为是隐式的(对于语言实现,而不是程序员!):编译器在传递时可能会忘记数组的大小在其他地方,依靠程序员知道,数组中有多少个对象。
这样做的好处是阵列访问非常简单;它们衰减到指针算术的问题,将索引与数组中对象的大小相乘,并将该偏移量添加到指针。这就是a[5]
与5[a]
完全相同的原因,它是*(a + 5)
的缩写。
另一个与性能相关的方面是从阵列制作子阵列非常简单:只需要计算起始地址。没有任何东西会迫使我们将数据复制到一个新的数组中,我们只需要记住使用正确的大小......
所以,是的,它在实现简单性和性能方面有深刻的理由,数组名称会以它们的方式衰减,我们应该为此感到高兴。