以下问题在大学编程竞赛中提出。我们被要求猜测输出和/或解释它的工作原理。不用说,我们都没有成功。
main(_){write(read(0,&_,1)&&main());}
一些简短的谷歌搜索让我想到了这个确切的问题,在codegolf.stackexchange.com
中提到:
https://codegolf.stackexchange.com/a/1336/4085
在那里,它解释了它做什么:Reverse stdin and place on stdout
,但不是如何。
我也在这个问题上找到了一些帮助:Three arguments to main, and other obfuscating tricks
但它仍然无法解释main(_)
,&_
和&&main()
的工作原理。
我的问题是, 这些语法是如何工作的?它们是我应该知道的,因为它们仍然相关吗?
如果不是直接的答案,我会很感激任何指针(资源链接等)。
答案 0 :(得分:26)
这个程序做了什么?
main(_){write(read(0,&_,1)&&main());}
在我们分析它之前,让我们美化它:
main(_) {
write ( read(0, &_, 1) && main() );
}
首先,你应该知道_
是一个有效的变量名,虽然是一个丑陋的变量名。让我们改变它:
main(argc) {
write( read(0, &argc, 1) && main() );
}
接下来,要认识到函数的返回类型和参数的类型在C中是可选的(但不是在C ++中):
int main(int argc) {
write( read(0, &argc, 1) && main() );
}
接下来,了解返回值的工作原理。对于某些CPU类型,返回值始终存储在相同的寄存器中(例如,x86上的EAX)。因此,如果省略return
语句,则返回值可能将是最近返回的函数。
int main(int argc) {
int result = write( read(0, &argc, 1) && main() );
return result;
}
对read
的调用或多或少是明显的:它从标准的(文件描述符0)读取到位于&argc
的内存中,用于1
字节。如果读取成功则返回1
,否则返回0.
&&
是逻辑“和”运算符。当且仅当它的左侧是“真”时(技术上,任何非零值),它评估其右侧。 &&
表达式的结果是int
,它始终为1(对于“true”)或0(对于false)。
在这种情况下,右侧会调用main
而不带参数。用1参数声明后调用main
没有参数是未定义的行为。然而,只要您不关心argc
参数的初始值,它通常会起作用。
然后将&&
的结果传递给write()
。所以,我们的代码现在看起来像:
int main(int argc) {
int read_result = read(0, &argc, 1) && main();
int result = write(read_result);
return result;
}
嗯。快速查看手册页显示write
有三个参数,而不是一个。另一种未定义行为的情况。就像调用main
参数太少一样,我们无法预测write
将获得的第二和第三个参数。在典型的计算机上,他们会得到的东西,但我们无法确定是什么。 (在非典型计算机上,可能会发生奇怪的事情。)作者依靠write
接收先前存储在内存堆栈中的内容。并且,他依靠 作为第二和第三个参数来阅读。
int main(int argc) {
int read_result = read(0, &argc, 1) && main();
int result = write(read_result, &argc, 1);
return result;
}
修正对main
的无效调用,添加标题,并展开我们拥有的&&
:
#include <unistd.h>
int main(int argc, int argv) {
int result;
result = read(0, &argc, 1);
if(result) result = main(argc, argv);
result = write(result, &argc, 1);
return result;
}
<小时/> 的结论强>
此程序在许多计算机上无法正常工作。即使您使用与原作者相同的计算机,它也可能无法在其他操作系统上运行。即使您使用相同的计算机和相同的操作系统,它也无法在许多编译器上运行。即使您使用相同的计算机编译器和操作系统,如果更改编译器的命令行标志,它也可能不起作用。
正如我在评论中所说,问题没有一个有效的答案。如果您发现比赛组织者或比赛裁判另有说明,请不要邀请他们参加下一场比赛。
答案 1 :(得分:9)
好的,_
只是在早期K&amp; R C语法中声明的变量,默认类型为int。它起到临时存储的作用。
程序将尝试从标准输入读取一个字节。如果有输入,它将以递归方式调用main继续读取一个字节。
在输入结束时,read(2)
将返回0,表达式将返回0,write(2)
系统调用将执行,并且调用链可能会展开。
我在这里说“可能”,因为从这一点来看,结果是高度依赖于实现的。缺少write(2)
的其他参数,但某些将位于寄存器和堆栈中,因此某些将被传递到内核中。相同的未定义行为适用于main
的各种递归激活的返回值。
在我的x86_64 Mac上,程序读取标准输入直到EOF,然后退出,完全没有写任何内容。