关于C#:了解main的一个不常见的参数

Understanding an uncommon argument to main

下面的问题是在一次大学节目比赛中提出的。我们被要求猜测输出和/或解释其工作。不用说,我们都没有成功。

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

一些简短的谷歌搜索让我找到了这个问题,在codegolf.stackexchange.com中问道:

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

在那里,它解释了它的作用:Reverse stdin and place on stdout,但没有解释它是如何做到的。

在这个问题上,我也找到了一些帮助:三个主要论点和其他模糊的技巧。但它仍然不能解释main(_)&_&&main()是如何工作的。

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

如果没有直接的答案,我会感激任何提示(指向资源链接等)。


这个程序是做什么的?

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

在分析之前,让我们先对它进行美化:

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

首先,您应该知道_是一个有效的变量名,尽管它很难看。让我们改变一下:

1
2
3
main(argc) {
    write( read(0, &argc, 1) && main() );
}

接下来,实现函数的返回类型和参数的类型在C中是可选的(而不是C++):

1
2
3
int main(int argc) {
    write( read(0, &argc, 1) && main() );
}

接下来,了解返回值是如何工作的。对于某些CPU类型,返回值总是存储在同一个寄存器中(例如,x86上的EAX)。因此,如果省略return语句,返回值可能是返回的最新函数。

1
2
3
4
int main(int argc) {
    int result = write( read(0, &argc, 1) && main() );
    return result;
}

read的调用或多或少是显而易见的:它从(文件描述符0)中的标准读取到位于&argc的内存中,用于1字节。如果读取成功,则返回1,否则返回0。

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

在这种情况下,右侧调用main,没有任何参数。用1个参数声明后,不带参数调用main是未定义的行为。不过,只要您不关心argc参数的初始值,它通常是有效的。

然后将&&的结果传递给write()。因此,我们的代码现在看起来是:

1
2
3
4
5
int main(int argc) {
    int read_result = read(0, &argc, 1) && main();
    int result = write(read_result);
    return result;
}

嗯。快速看一下手册页就可以发现,write有三个论点,而不是一个。另一种未定义行为的情况。就像用太少的参数调用main一样,我们无法预测write第二个和第三个参数会得到什么。在典型的计算机上,他们会得到一些东西,但我们不能确定是什么。(在不典型的计算机上,可能会发生奇怪的事情。)作者依靠write接收先前存储在内存堆栈上的内容。而且,他依赖于第二和第三个论点来阅读。

1
2
3
4
5
int main(int argc) {
    int read_result = read(0, &argc, 1) && main();
    int result = write(read_result, &argc, 1);
    return result;
}

修复对main的无效调用,添加头,扩展&&,我们有:

1
2
3
4
5
6
7
8
#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;
}


结论

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

正如我在评论中所说,这个问题没有一个有效的答案。如果你发现一个竞赛组织者或竞赛裁判说了别的话,不要邀请他们参加你的下一个比赛。


好的,_只是早期k&r c语法中声明的一个变量,默认类型为int。它用作临时存储器。

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

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

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

在我的x86 Mac上,程序读取标准输入直到EOF,然后退出,完全不写任何东西。