理解main的一个不常见的参数

Rau*_*akS 17 c argument-passing deobfuscation

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

main(_){write(read(0,&_,1)&&main());}
Run Code Online (Sandbox Code Playgroud)

一些简短的谷歌搜索引导我到这个确切的问题,问codegolf.stackexchange.com:

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

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

我也在这个问题上找到了一些帮助:主要的三个论点,以及其他混淆的技巧, 但它仍然没有解释如何main(_),&_并且&&main()有效.

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

如果不是直接的答案,我会感激任何指针(资源链接等).

Rob*_*obᵩ 26

这个程序做什么用的?

main(_){write(read(0,&_,1)&&main());}
Run Code Online (Sandbox Code Playgroud)

在我们分析它之前,让我们美化它:

main(_) {
    write ( read(0, &_, 1) && main() );
}
Run Code Online (Sandbox Code Playgroud)

首先,您应该知道这_是一个有效的变量名称,虽然是一个丑陋的名称.让我们改变它:

main(argc) {
    write( read(0, &argc, 1) && main() );
}
Run Code Online (Sandbox Code Playgroud)

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

int main(int argc) {
    write( read(0, &argc, 1) && main() );
}
Run Code Online (Sandbox Code Playgroud)

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

int main(int argc) {
    int result = write( read(0, &argc, 1) && main() );
    return result;
}
Run Code Online (Sandbox Code Playgroud)

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

&&是逻辑"和"运算符.当且仅当它的左侧是"真"时(技术上,任何非零值),它评估其右侧.&&表达式的结果int是始终为1(对于"true")或0(对于false).

在这种情况下,右侧main不带参数调用.调用main1个参数声明之后不带参数是不确定的行为.然而,它通常有效,只要您不关心argc参数的初始值.

然后将结果&&传递给write().所以,我们的代码现在看起来像:

int main(int argc) {
    int read_result = read(0, &argc, 1) && main();
    int result = write(read_result);
    return result;
}
Run Code Online (Sandbox Code Playgroud)

嗯.快速查看手册页显示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;
}
Run Code Online (Sandbox Code Playgroud)

修复无效调用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;
}
Run Code Online (Sandbox Code Playgroud)


结论

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

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


Dig*_*oss 9

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

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

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

我在这里说"可能",因为从这一点来看,结果是高度依赖于实现的.write(2)缺少其他参数,但有些东西将在寄存器和堆栈中,因此会将某些内容传递给内核.相同的未定义行为适用于各种递归激活的返回值main.

在我的x86_64 Mac上,程序读取标准输入直到EOF然后退出,根本不写任何内容.