int main(){
int array[] = [10,20,30,40,50] ;
printf("%d\n",-2[array -2]);
return 0 ;
}
谁能解释一下 -2[array-2] 是如何工作的以及为什么在这里使用 [ ] ?这是我作业中的一个问题,它给出了输出“-10”,但我不明白为什么?
int main(){
int array[] = [10,20,30,40,50] ;
printf("%d\n",-2[array -2]);
return 0 ;
}
谁能解释一下 -2[array-2] 是如何工作的以及为什么在这里使用 [ ] ?这是我作业中的一个问题,它给出了输出“-10”,但我不明白为什么?
从技术上讲,这会调用未定义的行为。引用C11
,第 6.5.6 章
如果指针操作数和结果都指向同一个数组对象的元素,或者超过数组对象的最后一个元素,则计算不应产生溢出;否则,行为未定义。[……]
所以,(array-2)
是未定义的行为。
但是,大多数编译器将读取索引,并且它可能会取消+2
和-2
索引, [2[a]
与a[2]
which is same as 相同*(a+2)
,因此,2[a-2]
is *((2)+(a-2))
],并且只考虑要评估的剩余表达式,即*(a)
or, a[0]
。
然后,检查运算符优先级
-2[array -2]
实际上与 相同-(array[0])
。因此,结果是 valuearray[0]
和-
ved。
这是一个不幸的指导示例,因为它暗示可以做一些在实践中经常起作用的不正确的事情。
技术上正确的答案是程序具有未定义的行为,因此任何结果都是可能的,包括打印 -10、打印不同的数字、打印不同的东西或根本不打印、运行失败、崩溃和/或做一些完全不相关的事情。
未定义的行为来自评估 subexpression array -2
。 array
从它的数组类型衰减到指向第一个元素的指针。 array -2
将指向之前两个位置的元素,但没有这样的元素(而且它不是“过去的最后一个”特殊规则),因此无论它出现在什么上下文中,评估它都是一个问题。
(C11 6.5.6/8 说)
当一个具有整数类型的表达式被添加到一个指针或从一个指针中减去时,......不得产生溢出;否则,行为未定义。
现在,讲师可能正在寻找的技术上不正确的答案是大多数实现中实际发生的情况:
即使array -2
在实际数组之外,它也会计算出某个地址,2*sizeof(int)
该地址是数组数据开始地址之前的字节。取消引用该地址是无效的,因为我们不知道那里实际上有任何地址int
,但我们不会这样做。
查看较大的表达式-2[array -2]
,[]
运算符的优先级高于一元运算-
符,因此它表示-(2[array -2])
and not (-2)[array -2]
。 A[B]
被定义为与 相同*((A)+(B))
。通常有A
一个指针值和B
一个整数值,但是像我们在这里做的那样颠倒使用它们也是合法的。所以这些是等价的:
-2[array -2]
-(2[array -2])
-(*(2 + (array - 2)))
-(*(array))
最后一步的行为与我们预期的一样:在该值之后的array - 2
is2*sizeof(int)
字节的地址值中添加两个,这使我们回到第一个数组元素的地址。因此*(array)
,取消引用该地址,给出 10,并-(*(array))
否定该值,给出 -10。程序打印 -10。
即使您观察到它在您的系统和编译器上“有效”,您也不应该指望这样的事情。由于该语言不保证会发生什么,因此如果您进行看起来不应该相关的轻微更改,或者在不同的系统、不同的编译器、同一编译器的不同版本或使用相同的系统和编译器在不同的日子。
以下-2[array-2]
是评估方式:
首先,请注意-2[array-2]
被解析为- (2[array-2])
. 下标运算符[...]
的优先级高于一元运算-
符。我们经常将常量-2
视为单个数字,但实际上它是-
应用于 a 的运算符2
。
在array-2
,array
中自动转换为指向其第一个元素的指针,因此它指向array[0]
。
然后array-2
尝试计算指向数组第一个元素之前的两个元素的指针。C 标准未定义结果行为,因为 C 2018 6.5.6 8 表示仅定义了指向数组成员和数组末尾的算术。
仅用于说明,假设我们使用的 C 实现通过定义指针来扩展 C 标准以使用平面地址空间并允许任意指针算术。然后array-2
在数组之前指向两个元素。
然后使用 C 标准定义为2[array-2]
的事实。即下标运算符是通过将两个东西相加并应用来实现的。因此,哪个表达式是,哪个是无关紧要。是一样的。也是如此。添加 2 将指针从数组之前的两个元素移回数组的开头。然后应用在该位置生成元素,即 10。E1[E2]
*((E1)+(E2))
*
E1
E2
E1+E2
E2+E1
2[array-2]
*(2 + (array-2))
*
最后,应用-
给出-10。(回想一下,这个结论只能通过我们假设 C 实现支持平面地址空间来实现。你不能在一般的 C 代码中使用它。)
此代码调用未定义的行为并可以打印任何内容,包括-10
.
C17 6.5.2.1 数组下标状态:
下标运算符 [] 的定义
E1[E2]
与(*((E1)+(E2)))
含义array[n]
等同于*((array) + (n))
编译器评估下标的方式。这使我们可以编写愚蠢的混淆,例如n[array]
100% 等效于array[n]
. 因为*((n) + (array))
相当于*((array) + (n))
。如此处所述:
对于数组,为什么 a[5] == 5[a] 会出现这种情况?
具体看表达式-2[array -2]
:
[array -2]
并且[array - 2]
自然等价。在这种情况下,前者只是为了混淆代码而故意使用的草率风格。[]
.-*( (2) + (array - 2) )
-
不是整数常量的一部分2
。C 不支持负整数常量1),-
实际上是一元减号运算符。[]
,因此-2[
“绑定”中的 2 到[
.根据 C17 6.5.6/8,子表达式(array - 2)
被单独评估并调用未定义的行为:
当一个整数类型的表达式被添加到指针或从指针中减去时,结果具有指针操作数的类型。/--/ 如果指针操作数和结果都指向同一个数组对象的元素,或者超过数组对象的最后一个元素,则计算不应产生溢出;否则,行为未定义。
推测性地,未定义行为的一种潜在形式可能是编译器决定将整个表达式替换为(2) + (array - 2)
,array
在这种情况下,整个表达式最终会变成-*array
并打印-10
。
对此没有任何保证,因此代码很糟糕。如果你被分配解释为什么代码打印-10
,你的老师是无能的。将混淆作为 C 研究的一部分进行研究不仅没有意义/有害,而且依赖未定义的行为或期望它给出特定结果也是有害的。
1) C 相当支持负整数常量表达式。-2
是一个整数常量表达式,其中2
是一个类型为 的整数常量int
。