3

所以我在 StackOverflow 上失去了我的周末,并在热门网络问题中看到了这个挑战

背景

你好高尔夫球手!我想学习所有的编程语言!但是我的注意力有点短……复制所有 Hello World 的例子很无聊……但我喜欢火!^w^

挑战

所以这是计划!我希望你们都编写最小的代码来编译,打印 Goodbye Cruel World!,然后崩溃。或者,作为额外的扭曲挑战,打印 Hello World!并与再见残酷世界崩溃!

作为一个愿意完全理解 C 语言的学生,当我偶然发现 C对这个挑战的答案时,我一直很困惑:

main(){puts(puts("Goodbye Cruel World!"));}

打印字符串,然后尝试将返回值用作指向要打印的另一个字符串的指针,这会导致分段错误。

感谢puts() 文档,我发现puts()成功返回非负值。因此,如果我理解正确,这相当于:

puts(2); 

2指向要打印的另一个字符串的指针”如何?

后来,对这个相同的答案进行了改进:

main(i){i=puts("Goodbye Cruel World!")/0;}

而这一次我完全迷失了。soi作为来自 main 的参数,用于存储 的返回值puts()。好的。但是\0呢?为什么使用NUL-TERMINATOR那里的字符?

如果你能让我轻松一点,我会很有趣地理解这一点。另外,我认为如果改写问题的标题可能会更准确,但我无法用语言表达我的误解。

4

3 回答 3

3

并回答您的第二个问题:

main(i){i=puts("Goodbye Cruel World!")/0;}

'\0'和之间有区别/0 。第一个是NUL字符,第二个是除以零。所以这段代码试图将结果puts除以零。

于 2017-06-11T13:20:26.533 回答
2

代码失败是因为参数的类型puts()const char *,意思是“指向只读的指针char”。

这是静态的,它不会仅仅因为您尝试传递其他东西而改变,而是函数将参数值解释为好像它是指向字符的指针(假设编译器甚至设法编译它,这是一个艰难的假设在这里,因为int返回的值不会转换为const char *)。

一般来说,像这样的小整数2在桌面/服务器类系统上(也不是在所有嵌入式系统上)作为指针无效,即该地址处的典型进程没有可用的内存,所以经常发生的情况是操作系统停止违反其界限的过程。但是,正如评论中提到的,这部分是undefined

于 2017-06-11T13:15:11.447 回答
2

两种解决方案都会导致未定义的行为。

第一个解决方案:

main(){puts(puts("Goodbye Cruel World!"));}

评估puts("Goodbye Cruel World!"),它在成功时返回一个非负值。这个值被传递给puts()。现在,根据§6.5.2.2 7

如果表示被调用函数的表达式具有包含原型的类型,则参数将隐式转换为相应参数的类型,就像通过 assignment 一样,将每个参数的类型作为其声明的非限定版本类型。

因此,代码尝试将第一次调用返回的值转换为puts()类型的值,就像通过赋值一样char *。这是赋值中左操作数的类型,而右操作数的类型是int。左操作数是指针类型,因此右操作数必须是指向兼容类型的限定或非限定版本的指针、指向 的指针void或空指针常量(§6.5.16.1 1)。这些都不是真的,所以这是违反约束的,编译器必须发出警告。

这是未定义的行为,因为没有定义运行此代码时应该发生的行为。

第二种解决方案也是未定义的行为,因为除以零会导致未定义的行为(§6.5.5 5):

/ 运算符的结果是第一个操作数除以第二个操作数的商;% 运算符的结果是余数。在这两种操作中,如果第二个操作数的值为零,则行为未定义。

未定义的行为可能包括也可能不包括程序的“崩溃”。

于 2017-06-11T13:35:28.403 回答