#include <stdio.h>
int main()
{
printf("%s", (1)["abcd"]+"efg"-'b'+1);
}
有人可以解释为什么这段代码的输出是:
fg
我知道(1)["abcd"]
指向"bcd"
,但为什么+"efg"-'b'+1
甚至是有效的语法?
答案 0 :(得分:10)
I know (1)["abcd"] points to "bcd"
没有。 (1)["abcd"]
是一个字符(b
)。
所以(1)["abcd"]+"efg"-'b'+1
是'b' + "egf" - 'b' + 1
,如果你简化它,那就变成"efg" + 1
。因此它打印fg
。
注意:以上答案仅解释了根据C语言规范严格合法的观察到的行为。这就是原因。
案例1: 'b' < 0
或'b' > 4
在这种情况下,表达式(1)["abcd"] + "efg" - 'b' + 1
将导致undefined behaviour,因为子表达式(1)["abcd"] + "efg"
,'b' + "efg"
产生无效的指针表达式( C11,6.5.5乘法运算符 - 引用如下。)
在广泛使用的ASCII字符集中,'b'
为十进制的98
;在不太广泛使用的EBCDIC字符集中,'b'
是十进制的130
。因此,子表达式(1)["abcd"] + "efg"
将在使用这两者之一的系统上导致未定义的行为。
禁止一个奇怪的架构,'b' <= 4 and 'b' >= 0
,这个程序会因为如何导致未定义的行为
C语言定义为:
C11,5.1.2.3程序执行
本国际标准中的语义描述描述了 抽象机器的行为,其中优化问题是 无关。 [...]在抽象机器中,所有表达式都是 按语义指定进行评估。实际的实施需求 如果它可以推导出它的值,则不评估表达式的一部分 不使用,不会产生副作用。
明确指出已根据抽象机器的行为定义了整个标准。
因此,在这种情况下,它确实会导致未定义的行为。
案例2: 'b' >= 0
或'b' <= 4
(这是非常虚构的,但从理论上讲,这是可能的)。
在这种情况下,子表达式(1)["abcd"] + "efg"
可以有效(反过来,整个表达式(1)["abcd"] + "efg" - 'b' + 1
)。
字符串文字"efg"
由4个字符组成,这是一个数组类型(C中的char[N]
类型),并且C标准保证(如上所述)指针表达式评估为1 -past数组的末尾不会溢出或导致未定义的行为。
以下是可能的子表达式,它们是有效的:
(1)"efg"+0
(2)"efg"+1
(3)"efg"+2
(4)"efg"+3
和(5)"efg"+4
因为C标准声明:
C11,6.5.5乘法运算符
添加或减去具有整数类型的表达式时 从指针开始,结果具有指针操作数的类型。如果 指针操作数指向数组对象的元素和数组 足够大,结果指向一个偏离的元素 原始元素使得下标的差异 结果和原始数组元素等于整数表达式。 换句话说,如果表达式P指向一个的第i个元素 数组对象,表达式(P)+ N(等效地,N +(P))和(P)-N (其中N具有值n)分别指向第i + n和第i 数组对象的第i个元素,只要它们存在即可。而且,如果 表达式P指向数组对象的最后一个元素,即 表达式(P)+1指向数组对象的最后一个元素, 如果表达式Q指向一个数组的最后一个元素 对象,表达式(Q)-1指向数组的最后一个元素 宾语。如果指针操作数和结果都指向元素 相同的数组对象,或一个超过数组的最后一个元素 对象,评估不得产生溢出;否则, 行为未定义。如果结果指向最后一个元素 对于数组对象,它不应该用作一元*的操作数 被评估的运算符。
在这种情况下,不导致未定义的行为。
感谢@zch&amp; @Keith Thompson用于挖掘C标准的相关部分:)
答案 1 :(得分:2)
其他两个答案之间的区别似乎有些混乱。这是一步一步发生的事情:
(1)["abcd"]+"efg"-'b'+1
第一部分(1)["abcd"]
利用了在C中处理数组的方式。让我们看看以下内容:
int a[5] = { 0, 10, 20, 30, 40 };
printf("%d %d\n", a[2], 2[a]);
输出为20 20
。为什么?因为int
数组的 name 计算其地址,其数据类型是指向int
的指针。引用整数数组的元素告诉C向数组的地址添加偏移量并将结果计算为类型int
。但这意味着C 不关心订单:a[2]
完全与2[a]
相同。
类似地,由于a
是数组的地址,a + 1
是第一次偏移到数组中的元素的地址。当然,这相当于1 + a
。
C中的字符串只是另一种人性化的方式,表示类型为char
的数组。因此(1)["abcd"]
与将第一个偏移处的元素返回到字符a
,b
,c
,d
,{{1 }} ...这是字符\0
。
在C中,每个字符都有一个整数值(通常是ASCII码)。 b
的值恰好是98.因此,评估的其余部分涉及使用整数和数组进行计算:字符串b
。
我们有字符串的地址。我们加上和减去98(字符"efg"
的ASCII值),我们加1. b
相互抵消,因此最终结果比第一个字符的地址多一个在字符串中,这是字符b
的地址。
f
中的%s
转换告诉C将地址视为字符串中的第一个字符,并打印整个字符串,直到它在末尾遇到空字符。
因此它打印printf()
,这是从fg
开始的字符串"efg"
的一部分。