【问题标题】:How does this program duplicate itself?这个程序如何自我复制?
【发布时间】:2026-02-12 16:55:01
【问题描述】:

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

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

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

【问题讨论】:

  • 3个warning and no error代表编译成功,为什么不运行呢?
  • @Cestarian 问题不在于它做什么 - 它是如何 做到的?因此,标题。
  • 这实际上不是最短的程序。实际最短的是 0 字节长。您可以让编译器成功地将一个 0 字节的 c 文件编译成可执行文件。运行该 exe 会打印 0 个字节,这是原始程序的完整源代码。
  • @GrantPeters:可以吗?如何?空源文件是有效的翻译单元,但不是有效的源文件。
  • @KeithThompson 请参阅 *.com/questions/17515790/… 了解使用此功能的 ioccc 条目

标签: c quine


【解决方案1】:

这个程序依赖于假设

  • 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赋值表达式 的值也将是 "main(a){printf(a,34,a=%c%s%c,34);}",按照 C标准--C11: 6.5.16

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

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

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

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

它比4 字符更长,但至少跟在 K&R C 后面。

【讨论】:

    【解决方案2】:

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

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

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

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

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

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

    【讨论】:

    • 是的,由于评估顺序,存在 UB。还有更多的 UB,因为 a (int) 的类型与 %s 转换所期望的类型 (char *) 不同。
    • @zneak 谢谢,现在我明白了这个程序,你真的很擅长 c
    • @JerryCoffin;什么是“更多”或“更少”UB?
    • @hacks:未定义行为的另一个实例,因此即使(例如)答案中引用的 UB 以某种方式修复,整个程序仍然会有 UB。 IOW,不是“(更多未定义的)行为,而是“未定义的(更多行为)”。
    【解决方案3】:

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

    main(a) { ...
    

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

    int main(int a) { ...
    

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

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

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

    这是原始程序。多田!

    【讨论】:

      【解决方案4】:

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

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

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

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

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

      【讨论】: