#include <stdio.h>
int main()
{
printf("%s", (1)["abcd"]+"efg"-'b'+1);
}
有人可以解释为什么这段代码的输出是:
fg
我知道(1)["abcd"]
点,"bcd"
但为什么+"efg"-'b'+1
甚至是有效的语法?
I know (1)["abcd"] points to "bcd"
No.(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
将导致未定义的行为,因为子表达式(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 个字符组成,它是一个数组类型(char[N]
C 中的类型),并且 C 标准保证(如上面引用的)计算到数组末尾的指针表达式不会溢出或导致未定义的行为。
以下是可能的子表达式并且它们是有效的: (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-n 个元素,前提是它们存在。此外,如果表达式 P 指向数组对象的最后一个元素,则表达式 (P)+1 指向数组对象的最后一个元素,如果表达式 Q 指向数组对象的最后一个元素,则表达式 (Q)-1 指向数组对象的最后一个元素。如果指针操作数和结果都指向同一个数组对象的元素,或者超过数组对象的最后一个元素,则计算不应产生溢出;否则,行为未定义。如果结果指向数组对象的最后一个元素,则不应将其用作计算的一元 * 运算符的操作数。否则,行为未定义。如果结果指向数组对象的最后一个元素,则不应将其用作计算的一元 * 运算符的操作数。否则,行为未定义。如果结果指向数组对象的最后一个元素,则不应将其用作计算的一元 * 运算符的操作数。
所以在这种情况下它不会导致未定义的行为。
感谢@zch和@Keith Thompson挖掘出 C 标准的相关部分 :)
其他两个答案之间的区别似乎有些混乱。这是发生的事情,一步一步:
(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
计算其地址,并且其数据类型是指向int
. 引用整数数组的元素告诉 C 向数组的地址添加一个偏移量,并将结果评估为 type int
。但这意味着 C不关心顺序:与.a[2]
2[a]
同样,sincea
是数组的地址,是数组a + 1
中第一个偏移量处元素的地址。当然,这相当于1 + a
.
C 中的字符串只是表示类型数组的另一种对人类友好的方式char
。所以(1)["abcd"]
与将第一个偏移处的元素返回到字符数组中是相同的a
,b
, c
, d
, \0
... 就是字符b
。
在 C 中,每个字符都有一个整数值(通常是它的 ASCII 码)。的值b
恰好是 98。因此,评估的其余部分涉及整数和数组的计算:字符串"efg"
。
我们有字符串的地址。我们加减 98(字符 的 ASCII 值b
),然后加 1 b
。字符f
。
中的%s
转换printf()
告诉 C 将地址视为字符串中的第一个字符,并打印整个字符串,直到最后遇到空字符。
所以它会打印,这是从 .开始fg
的字符串的一部分。"efg"
f