所以我在StackOverflow上丢失了我的周末,并在this challenge中看到Hot Network Questions。
背景
Hello高尔夫球手!我想学习所有的编程语言! 但我有一个短暂的注意力......并且复制了所有的Hello 世界的例子变得无聊......但我喜欢火! ^ W ^
挑战
所以这是计划!我希望你们都写出最小的代码 将编译,打印Goodbye Cruel World!,然后崩溃。或者,作为一个 奖金扭曲挑战,打印Hello World!和Goodbye Cruel一起崩溃 世界!
作为一个愿意完全理解C语言的学生,当我遇到C answer这个挑战时,我一直很困惑:
main(){puts(puts("Goodbye Cruel World!"));}
打印字符串,然后尝试将返回值用作指针 到另一个要打印的字符串,这会导致分段错误。
感谢puts()
documentation我发现puts()
会在成功时返回非负值。因此,如果我理解正确,这相当于:
puts(2);
2
如何“指向另一个要打印的字符串的指针”??
后来,这个答案得到了改进:
main(i){i=puts("Goodbye Cruel World!")/0;}
而这次我完全迷失了。因此,i
被视为来自main的参数,用于存储puts()
的返回值。好。但是\0
呢?为什么在那里使用NUL-TERMINATOR
字符?
如果你可以请我稍微放松一下,对我来说理解这一点会非常有趣。此外,我认为问题的标题如果改写可能会更准确,但我无法用言语表达我的误解。
答案 0 :(得分:3)
回答你的第二个问题:
main(i){i=puts("Goodbye Cruel World!")/0;}
'\0'
和/0
之间存在差异
第一个是NUL
字符,但第二个是零除。因此,此代码尝试将puts
的结果除以零。
答案 1 :(得分:2)
代码失败,因为puts()
的参数类型为const char *
,表示“指向只读char
”。
这是静态的,它不会因为你试图传递别的东西而改变,而是函数将参数值解释为它是一个指向字符的指针(假设编译器甚至设法编译它,这是这是一个艰难的假设,因为返回的int
值不会转换为const char *
)。
一般来说,像2
这样的小整数在桌面/服务器级系统上是无效的(而不是所有嵌入式系统上的指针),即该地址的典型进程没有可用的内存,所以经常发生的是操作系统停止违反其边界的过程。但是,正如评论中提到的那样,这部分是 undefined 。
答案 2 :(得分:2)
两种解决方案都会导致未定义的行为。
第一个解决方案:
main(){puts(puts("Goodbye Cruel World!"));}
评估puts("Goodbye Cruel World!")
,它会在成功时返回非负值。该值传递给puts()
。现在,根据§6.5.2.2 7:
如果表示被调用函数的表达式具有类型 确实包含一个原型,参数被隐式转换, as 如果通过赋值,则取相应参数的类型 每个参数的类型是其不合格的版本 声明的类型。
因此,代码尝试将第一次调用返回的值转换为puts()
,就像通过赋值一样,转换为类型char *
的值。这是赋值中左操作数的类型,而右操作数的类型是int
。左操作数是指针类型,因此右操作数必须是指向兼容类型的限定或非限定版本的指针,指向void
的指针或空指针常量(§6.5.16.1 1)。这些都不是真的,所以这是违反约束的,编译器必须发出警告。
这是未定义的行为,因为没有为运行此代码时应该发生的行为定义的行为。
第二种解决方案也是未定义的行为,因为除以零会导致未定义的行为(§6.5.5 5):
/运算符的结果是来自分区的商 第一个操作数由第二个操作数; %运算符的结果是 剩余。在两个操作中,如果第二个操作数的值是 零,行为未定义。
未定义的行为可能包括也可能不包括"崩溃"你的计划。