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

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


当前回答

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

是的,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之外。

其他回答

当然,还有

 ("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,则可以使用不同的汇编级优化,开发人员再次必须使其显式,因为编译器无法识别它

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

我记得编译器设计原理,假设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位置返回我的对象。

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

是的,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之外。

C标准对[]运算符的定义如下:

a[b]==*(a+b)

因此,[5]将评估:

*(a + 5)

并且5[a]将评估:

*(5 + a)

a是指向数组的第一个元素的指针。a[5]是距离a更远的5个元素的值,与*(a+5)相同,从小学数学中我们知道它们是相等的(加法是可交换的)。

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