正如Joel在Stack Overflow播客#34中指出的,在C编程语言(又名:K&R)中,提到了C:a[5]==5[a]中数组的这个属性

乔尔说这是因为指针运算,但我仍然不明白。为什么a[5]==5[a]?


当前回答

关于Dinah的sizeof问题,似乎没有人提到过一件事:

只能向指针添加整数,不能将两个指针相加。这样,当将指针添加到整数或将整数添加到指针时,编译器总是知道需要考虑哪个位的大小。

其他回答

当然,还有

 ("ABCD"[2] == 2["ABCD"]) && (2["ABCD"] == 'C') && ("ABCD"[2] == 'C')

其主要原因是,在70年代设计C时,计算机没有太多内存(64KB是很多),因此C编译器没有做太多语法检查。因此,“X[Y]”相当盲目地翻译为“*(X+Y)”

这也解释了“+=”和“++”语法。“A=B+C”形式的所有对象都具有相同的编译形式。但是,如果B是与A相同的对象,则可以进行汇编级优化。但是编译器不够明亮,无法识别它,所以开发人员不得不(A+=C)。类似地,如果C为1,则可以使用不同的汇编级优化,开发人员再次必须使其显式,因为编译器无法识别它

我认为其他答案遗漏了一些东西。

是的,p[i]根据定义等价于*(p+i),*(i+p)(因为加法是可交换的)等效于*(i+p),而*(i+p)(同样,根据[]算子的定义)等效于i[p]。

(在array[i]中,数组名称隐式转换为指向数组第一个元素的指针。)

但在这种情况下,加法的交换性并不那么明显。

当两个操作数都是同一类型,甚至是不同的数字类型,并被提升为一个公共类型时,交换性就非常有意义:x+y==y+x。

但在本例中,我们具体讨论的是指针算术,其中一个操作数是指针,另一个是整数。(整数+整数是不同的操作,指针+指针是无意义的。)

C标准对+运算符的描述(N1570 6.5.6)表示:

对于加法,两个操作数都应为算术类型,或一个操作数应是指向完整对象类型的指针应为整数类型。

它可以很容易地说:

对于加法,两个操作数都应为算术类型,或左侧操作数应是指向完整对象类型和正确操作数的指针应为整数类型。

在这种情况下,i+p和i[p]都是非法的。

在C++术语中,我们确实有两组重载+运算符,可以粗略地描述为:

pointer operator+(pointer p, integer i);

and

pointer operator+(integer i, pointer p);

其中只有第一个是真正必要的。

那么为什么会这样呢?

C++继承了C的这一定义,后者从B获得了它(数组索引的交换性在1972年的《用户参考B》中明确提到),后者从BCPL(1967年的手册)获得了它,BCPL很可能是从更早的语言(CPL?Algol?)获得的。

因此,数组索引是用加法来定义的,即使是指针和整数的加法,也是可交换的,这一想法可以追溯到几十年前,直到C的祖先语言。

这些语言比现代C语言的强类型少得多。特别是,指针和整数之间的区别经常被忽略。(在无符号关键字被添加到语言中之前,早期的C程序员有时将指针用作无符号整数。)因此,由于操作数的类型不同,所以使加法不可交换的想法可能不会出现在这些语言的设计者身上。如果用户想添加两个“东西”,无论这些“东西”是整数、指针还是其他东西,都不能由语言来阻止。

多年来,对该规则的任何修改都会破坏现有的代码(尽管1989年的ANSI C标准可能是一个很好的机会)。

更改C和/或C++以要求将指针放在左边,将整数放在右边,这可能会破坏一些现有代码,但不会损失真正的表达能力。

所以现在我们有了arr[3]和3[arr],意思完全相同,尽管后一种形式不应该出现在IOCCC之外。

现在有点历史了。在其他语言中,BCPL对C的早期发展产生了相当大的影响。如果您在BCPL中声明的数组类似于:

let V = vec 10

实际上分配了11个字的内存,而不是10个。通常V是第一个,并包含紧接其后的单词的地址。因此,与C不同,命名V到那个位置,并获取数组第0个元素的地址。因此,BCPL中的数组间接寻址表示为

let J = V!5

真的不得不做J=!(V+5)(使用BCPL语法),因为需要获取V以获得阵列的基地址。因此,V!5和5!V是同义词。据坊间观察,WAFL(Warwick Functional Language,沃里克函数语言)是用BCPL编写的,据我所知,在访问用作数据存储的节点时,倾向于使用后一种语法而不是前一种语法。当然这是35到40年前的某个地方,所以我的记忆有点生疏

省去了额外的存储字,并在命名数组时让编译器插入数组的基地址,这一创新是后来才出现的。根据C历史论文,这大约发生在C中添加结构的时候。

注意!BCPL中既有一元前缀运算符,也有二元中缀运算符,在这两种情况下都是间接操作。只是二进制形式在执行间接操作之前包括两个操作数的相加。鉴于BCPL(和B)面向单词的性质,这实际上很有意义。当C获得数据类型时,“指针和整数”的限制就变得必要了,sizeof也成了一件事。

因为数组访问是根据指针定义的。a[i]被定义为表示*(a+i),它是可交换的。

我知道问题得到了答案,但我忍不住分享了这个解释。

我记得编译器设计原理,假设a是一个int数组,int的大小为2字节,&a的基址为1000。

[5]将如何工作->

Base Address of your Array a + (5*size of(data type for array a))
i.e. 1000 + (5*2) = 1010

So,

类似地,当c码被分解为3地址码时,5[a]将变为->

Base Address of your Array a + (size of(data type for array a)*5)
i.e. 1000 + (2*5) = 1010 

所以基本上这两个语句都指向内存中的相同位置,因此a[5]=5[a]。

这一解释也是数组中负索引在C中工作的原因。

即,如果我访问[-5],它会给我

Base Address of your Array a + (-5 * size of(data type for array a))
i.e. 1000 + (-5*2) = 990

它将在990位置返回我的对象。