为什么(1)[“abcd”] +“efg” - 'b'+ 1变成“fg”?

时间:2013-10-19 23:11:47

标签: c char printf

#include <stdio.h>
int main()
{
    printf("%s", (1)["abcd"]+"efg"-'b'+1);
}

有人可以解释为什么这段代码的输出是:

fg

我知道(1)["abcd"]指向"bcd",但为什么+"efg"-'b'+1甚至是有效的语法?

2 个答案:

答案 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"]与将第一个偏移处的元素返回到字符abcd,{{1 }} ...这是字符\0

在C中,每个字符都有一个整数值(通常是ASCII码)。 b的值恰好是98.因此,评估的其余部分涉及使用整数和数组进行计算:字符串b

我们有字符串的地址。我们加上和减去98(字符"efg"的ASCII值),我们加1. b相互抵消,因此最终结果比第一个字符的地址多一个在字符串中,这是字符b的地址。

f中的%s转换告诉C将地址视为字符串中的第一个字符,并打印整个字符串,直到它在末尾遇到空字符。

因此它打印printf(),这是从fg开始的字符串"efg"的一部分。