这个程序如何自我复制?
How does this program duplicate itself?
此代码来自 Hacker's Delight。说这是C语言中最短的此类程序,长度为64个字符,但我不明白:
main(a){printf(a,34,a="main(a){printf(a,34,a=%c%s%c,34);}",34);}
我试着编译了一下。它编译时有 3 个警告,没有错误。
它依赖于 C 语言的几个怪癖和(我认为是)未定义的行为。
首先,它定义了main
函数。声明一个没有 return 类型或参数类型的函数是合法的,它们将被假定为 int
。这就是 main(a){
部分起作用的原因。
然后,它用 4 个参数调用 printf
。由于它没有原型,它被假定为 return int
并接受 int
参数(除非你的编译器隐式地声明它,就像 Clang 那样)。
第一个参数假定为int
,在程序开头为argc
。第二个参数是 34(这是双引号字符的 ASCII)。第三个参数是一个赋值表达式,它将格式字符串赋值给 a
和 return 。它依赖于指针到整数的转换,这在 C 中是合法的。最后一个参数是数字形式的另一个引号字符。
在运行时,%c
格式说明符替换为引号,%s
替换为格式字符串,您再次获得原始来源。
据我所知,参数评估的顺序未定义。这个 quine 之所以有效,是因为赋值 a="main(a){printf(a,34,a=%c%s%c,34);}"
在 a
作为第一个参数传递给 printf
之前被评估,但据我所知,没有强制执行它的规则。此外,这在 64 位平台上不起作用,因为指针到 int 的转换会将指针截断为 32 位值。事实上,尽管我可以看到它在某些平台上是如何工作的,但它在我的计算机上使用我的编译器却无法工作。
该程序应该打印它自己的代码。请注意字符串文字与整个程序代码的相似性。这个想法是文字将用作 printf()
格式字符串,因为它的值被分配给变量 a
(尽管在参数列表中)并且它也将作为要打印的字符串传递(因为赋值表达式的计算结果为分配的值)。 34
是双引号字符("
)的ASCII码;使用它可以避免包含转义文字引号字符的格式字符串。
该代码依赖于函数参数求值顺序形式的未指定行为。如果它们按参数列表顺序求值,那么程序可能会失败,因为 a
的值将在实际分配给它正确的值之前用作指向格式字符串的指针。
此外,a
的类型默认为int
,并且无法保证int
的宽度足以容纳对象指针而不截断它。
另外,C标准只为main()
指定了两个允许的签名,而使用的签名不在其中。
此外,编译器在没有原型的情况下推断出的 printf()
类型是不正确的。绝不保证编译器会生成适用于它的调用序列。
这基于 C 允许您执行的许多怪癖,以及一些恰好对您有利的未定义行为。依次为:
main(a) { ...
如果未指定类型,则假定为 int
,因此这等同于:
int main(int a) { ...
尽管 main
应该接受 0 个或 2 个参数,这是未定义的行为,但可以允许这只是忽略缺少的第二个参数。
接下来是正文,我会 space 出来。请注意 a
是 int
根据 main
:
printf(a,
34,
a = "main(a){printf(a,34,a=%c%s%c,34);}",
34);
参数的求值顺序未定义,但我们依赖于第 3 个参数 - 赋值 - 首先被求值。我们还依赖于能够将 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);}
这是原始程序。多田!
该程序基于以下假设
- return
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
和赋值表达式的值根据 C 标准, 也将是 "main(a){printf(a,34,a=%c%s%c,34);}"
--C11: 6.5.16
An assignment operator stores a value in the object designated by the left operand. An assignment expression has the value of the left operand after the assignment [...]
考虑到上述赋值运算符的语义,程序将扩展为
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。
此代码来自 Hacker's Delight。说这是C语言中最短的此类程序,长度为64个字符,但我不明白:
main(a){printf(a,34,a="main(a){printf(a,34,a=%c%s%c,34);}",34);}
我试着编译了一下。它编译时有 3 个警告,没有错误。
它依赖于 C 语言的几个怪癖和(我认为是)未定义的行为。
首先,它定义了main
函数。声明一个没有 return 类型或参数类型的函数是合法的,它们将被假定为 int
。这就是 main(a){
部分起作用的原因。
然后,它用 4 个参数调用 printf
。由于它没有原型,它被假定为 return int
并接受 int
参数(除非你的编译器隐式地声明它,就像 Clang 那样)。
第一个参数假定为int
,在程序开头为argc
。第二个参数是 34(这是双引号字符的 ASCII)。第三个参数是一个赋值表达式,它将格式字符串赋值给 a
和 return 。它依赖于指针到整数的转换,这在 C 中是合法的。最后一个参数是数字形式的另一个引号字符。
在运行时,%c
格式说明符替换为引号,%s
替换为格式字符串,您再次获得原始来源。
据我所知,参数评估的顺序未定义。这个 quine 之所以有效,是因为赋值 a="main(a){printf(a,34,a=%c%s%c,34);}"
在 a
作为第一个参数传递给 printf
之前被评估,但据我所知,没有强制执行它的规则。此外,这在 64 位平台上不起作用,因为指针到 int 的转换会将指针截断为 32 位值。事实上,尽管我可以看到它在某些平台上是如何工作的,但它在我的计算机上使用我的编译器却无法工作。
该程序应该打印它自己的代码。请注意字符串文字与整个程序代码的相似性。这个想法是文字将用作 printf()
格式字符串,因为它的值被分配给变量 a
(尽管在参数列表中)并且它也将作为要打印的字符串传递(因为赋值表达式的计算结果为分配的值)。 34
是双引号字符("
)的ASCII码;使用它可以避免包含转义文字引号字符的格式字符串。
该代码依赖于函数参数求值顺序形式的未指定行为。如果它们按参数列表顺序求值,那么程序可能会失败,因为 a
的值将在实际分配给它正确的值之前用作指向格式字符串的指针。
此外,a
的类型默认为int
,并且无法保证int
的宽度足以容纳对象指针而不截断它。
另外,C标准只为main()
指定了两个允许的签名,而使用的签名不在其中。
此外,编译器在没有原型的情况下推断出的 printf()
类型是不正确的。绝不保证编译器会生成适用于它的调用序列。
这基于 C 允许您执行的许多怪癖,以及一些恰好对您有利的未定义行为。依次为:
main(a) { ...
如果未指定类型,则假定为 int
,因此这等同于:
int main(int a) { ...
尽管 main
应该接受 0 个或 2 个参数,这是未定义的行为,但可以允许这只是忽略缺少的第二个参数。
接下来是正文,我会 space 出来。请注意 a
是 int
根据 main
:
printf(a,
34,
a = "main(a){printf(a,34,a=%c%s%c,34);}",
34);
参数的求值顺序未定义,但我们依赖于第 3 个参数 - 赋值 - 首先被求值。我们还依赖于能够将 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);}
这是原始程序。多田!
该程序基于以下假设
- return
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
和赋值表达式的值根据 C 标准, 也将是 "main(a){printf(a,34,a=%c%s%c,34);}"
--C11: 6.5.16
An assignment operator stores a value in the object designated by the left operand. An assignment expression has the value of the left operand after the assignment [...]
考虑到上述赋值运算符的语义,程序将扩展为
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。