13

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

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

我试图编译它。它编译时有 3 个警告并且没有错误。

4

4 回答 4

7

该程序依赖于以下假设

  • 返回类型mainint
  • 函数的参数类型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的值也将按照 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);}  

它的4字符更长,但至少遵循 K&R C。

于 2015-04-24T02:25:42.697 回答
5

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

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

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

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

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

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

于 2015-04-24T01:56:48.463 回答
4

这基于 C 允许你做的许多怪癖,以及一些恰好对你有利的未定义行为。为了:

main(a) { ...

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

int main(int a) { ...

即使main应该采用 0 或 2 个参数,这是未定义的行为,也可以忽略缺少的第二个参数。

接下来是身体,我将把它隔开。请注意,这a是一个intas per main

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

参数的评估顺序是未定义的,但我们依赖于第三个参数 - 赋值 - 首先得到评估。我们还依赖于能够将 a 分配给 a 的未定义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);}

这是原始程序。多田!

于 2015-04-24T02:21:25.073 回答
2

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

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

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

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

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

于 2015-04-24T01:57:38.040 回答