18

以下问题是在一次大学编程竞赛中给出的。我们被要求猜测输出和/或解释它的工作原理。不用说,我们都没有成功。

main(_){write(read(0,&_,1)&&main());}

一些简短的谷歌搜索让我想到了这个确切的问题,在codegolf.stackexchange.com

https://codegolf.stackexchange.com/a/1336/4085

在那里,它解释它的作用:Reverse stdin and place on stdout,但不是如何

我还在这个问题中找到了一些帮助:Main 的三个参数和其他混淆技巧, 但它仍然没有解释如何main(_)工作。&_&&main()

我的问题是,这些语法是如何工作的?它们是我应该知道的,比如它们是否仍然相关?

如果不是直接的答案,我将不胜感激任何指针(指向资源链接等)。

4

2 回答 2

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或多或少很明显:它从标准 in(文件描述符 0)读取到位于 的内存中&argc,以获取1字节。1如果读取成功则返回,否则返回 0。

&&是逻辑“与”运算符。当且仅当它的左侧为“真”(从技术上讲,任何非零值)时,它才会评估其右侧。&&表达式的结果int是始终为 1(表示“真”)或 0(表示假)的 an。

在这种情况下,右侧调用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;
}


结论

该程序在许多计算机上无法按预期工作。即使您使用与原作者相同的计算机,它也可能无法在不同的操作系统上运行。即使您使用相同的计算机和相同的操作系统,它也无法在许多编译器上运行。即使您使用相同的计算机编译器和操作系统,如果您更改编译器的命令行标志,它也可能无法正常工作。

正如我在评论中所说,这个问题没有有效的答案。如果您发现比赛组织者或比赛评委另有说法,请不要邀请他们参加您的下一场比赛。

于 2012-04-25T18:24:23.747 回答
9

好的,_它只是早期 K&R C 语法中声明的变量,默认类型为 int。它起到临时存储的作用。

程序将尝试从标准输入中读取一个字节。如果有输入,它将递归调用 main 继续读取一个字节。

在输入结束时,read(2)将返回 0,表达式将返回 0,write(2)系统调用将执行,调用链可能会展开。

我在这里说“可能”,因为从这一点开始,结果高度依赖于实现。其他参数write(2)丢失了,但寄存器和堆栈中会有一些东西,所以一些东西被传递到内核中。相同的未定义行为适用于 的各种递归激活的返回值。main

在我的 x86_64 Mac 上,程序读取标准输入直到 EOF 然后退出,什么也不写。

于 2012-04-25T18:13:49.173 回答