这个程序如何复制自己?

时间:2015-04-24 01:29:24

标签: c quine

此代码来自Hacker's Delight。它说这是C中最短的程序,长度为64个字符,但我不明白:

    main(a){printf(a,34,a="main(a){printf(a,34,a=%c%s%c,34);}",34);}

我试着编译它。它汇编了3个警告,没有错误。

4 个答案:

答案 0 :(得分:7)

该计划依赖于

的假设
  • main的返回类型为int
  • 函数的参数类型默认为int
  • 将首先评估参数a="main(a){printf(a,34,a=%c%s%c,34);}"

它将调用未定义的行为在C 中无法保证函数参数的评估顺序 尽管如此,该计划的工作原理如下:

赋值表达式 a="main(a){printf(a,34,a=%c%s%c,34);}"会将字符串"main(a){printf(a,34,a=%c%s%c,34);}"分配给a赋值表达式的值将为{ {1}}根据C标准--C11:6.5.16

  

赋值运算符将值存储在左操作数指定的对象中。分配后的赋值表达式 具有左操作数的值

考虑到上述赋值运算符的语义,程序将被扩展为

"main(a){printf(a,34,a=%c%s%c,34);}"

ASCII main(a){ printf("main(a){printf(a,34,a=%c%s%c,34);}",34,a="main(a){printf(a,34,a=%c%s%c,34);}",34); } 34。说明符及其相应的参数:

"

更好的版本是

%c ---> 34 
%s ---> "main(a){printf(a,34,a=%c%s%c,34);}" 
%c ---> 34  

main(a){a="main(a){a=%c%s%c;printf(a,34,a,34);}";printf(a,34,a,34);} 字符更长,但至少在K& R C之后。

答案 1 :(得分:5)

它依赖于C语言的几个怪癖和(我认为)未定义的行为。

首先,它定义了main函数。声明没有返回类型或参数类型的函数是合法的,并且它们将被假定为int。这就是main(a){部分有效的原因。

然后,它用4个参数调用printf。由于它没有原型,因此假定返回int并接受int参数(除非你的编译器隐式声明它,否则就像Clang那样)。

第一个参数假定为int,并且在程序开头为argc。第二个参数是34(双引号字符的ASCII)。第三个参数是赋值表达式,它将格式字符串分配给a并返回它。它依赖于指向int的转换,这在C语言中是合法的。最后一个参数是数字形式的另一个引号字符。

在运行时,%c格式说明符用引号替换,%s替换为格式字符串,然后再次获得原始源。

据我所知,论证评估的顺序是不确定的。这个问题可行,因为在a="main(a){printf(a,34,a=%c%s%c,34);}"作为a的第一个参数传递之前评估了作业printf,但据我所知,没有规则可以强制执行它。此外,这不能在64位平台上工作,因为指针到int的转换会将指针截断为32位值。事实上,尽管我可以看到它在某些平台上是如何工作的,但它并不能用我的编译器在我的计算机上运行。

答案 2 :(得分:4)

这是基于C允许你做的许多怪癖,以及一些对你有利的未定义行为。按顺序:

main(a) { ...

如果未指定,则假定类型为int,因此这相当于:

int main(int a) { ...

即使main应该采用0或2个参数,并且这是未定义的行为,也可以允许这样做,只是忽略丢失的第二个参数。

接下来,身体,我将展开。请注意,根据aintmain

printf(a,
       34,
       a = "main(a){printf(a,34,a=%c%s%c,34);}",
       34);

参数的评估顺序是未定义的,但我们依赖于第三个参数 - 赋值 - 首先进行评估。我们还依赖于能够将char *分配给int的未定义行为。另请注意,34是"的ASCII值。因此,该计划的预期影响是:

int main(int a, char** ) {
    printf("main(a){printf(a,34,a=%c%s%c,34);}",
           '"',
           "main(a){printf(a,34,a=%c%s%c,34);}",
           '"');
    return 0; // also left off
}

在评估时,产生:

main(a){printf(a,34,a="main(a){printf(a,34,a=%c%s%c,34);}",34);}

这是原始程序。多田!

答案 3 :(得分:2)

程序假设打印自己的代码。请注意字符串文字与整个程序代码的相似性。这个想法是文字将被用作printf()格式字符串,因为它的值被赋值给变量a(虽然在参数列表中),并且它也将作为字符串传递给print(因为赋值表达式的计算结果为已分配的值。 34是双引号字符(")的ASCII码;使用它可以避免使用包含转义的文字引号字符的格式字符串。

代码依赖于函数参数求值顺序形式的未指定行为。如果以参数列表顺序评估它们,则程序可能会失败,因为a的值将在实际分配给它之前用作指向格式字符串的指针。

此外,a的类型默认为int,并且无法保证int足够宽以容纳对象指针而不截断它。

此外,C标准仅为main()指定了两个允许的签名,并且使用的签名不在其中。

此外,编译器在没有原型的情况下推断的printf()类型不正确。绝不保证编译器将生成适用于它的调用序列。