c-这个程序是如何自我复制的



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

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

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

该程序依赖于

  • main的返回类型为int
  • 函数的参数类型默认为int
  • 将首先评估自变量CCD_ 4

它将调用未定义的行为C中不能保证函数参数的求值顺序
尽管如此,这个程序的工作原理如下:

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

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

考虑到分配运算符的上述语义,程序将扩展为

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);
}  

ASCII34"。规范及其相应的参数:

%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);}  

它比CCD_ 11字符长,但至少跟在K&R C.

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

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

然后,它用4个参数调用printf。由于它没有原型,所以假设它返回int并接受int参数(除非编译器像Clang那样隐式声明它)。

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

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

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

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

main(a) { ...

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

int main(int a) { ...

尽管main应该接受0或2个参数,并且这是未定义的行为,但这可以被允许为忽略丢失的第二个参数。

接下来是身体,我将把它隔开。注意,根据main:,aint

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);}

这是最初的程序。塔达!

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

代码依赖于函数参数求值顺序形式的未指定行为。如果按照参数列表顺序对它们进行评估,那么程序很可能会失败,因为在实际分配正确值之前,a的值将被用作格式字符串的指针

此外,a的类型默认为a="main(a){printf(a,34,a=%c%s%c,34);}"0,并且不能保证int的宽度足以容纳对象指针而不截断它

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

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

最新更新