IOCCC 1984/decot.c - 它能在21世纪编译吗?

10
这段有趣的代码是在国际混淆C代码大赛的第一届 (1984) 中展示的: http://www.ioccc.org/years.html#1984 (decot)
在清除预处理器滥用和由goto语句和一些狡猾注释导致的未使用代码的残骸后,你最终得到了以下幸存的代码(如果我错了,请纠正我!):
#include <stdio.h> //used to suppress warnings
#include <math.h> //used to suppress warnings
extern int fl00r; //renamed to not clash with floor from math.h - unless it's part of the trickery???
int b, k['a'] = {
    sizeof(int(*)()),
    };
struct tag {int x0,*xO;}

*main(int i, int dup, int signal) { //int added to suppress warnings
  for(signal=0;*k *= * __FILE__ *i;) {
   printf(&*"'\",=);    /*\n\\", (*((int(*)())&fl00r))(i)); //see line 3
   if(b&&k+sin(signal)/ * ((main) (b)-> xO));
  }
}

还有一个编译错误需要解决:

decot.c: In function 'main':
decot.c:12:28: error: too few arguments to function 'main'
   12 |    if(b&&k+sin(signal)/ * ((main) (b)-> xO));
      |                            ^
decot.c:9:2: note: declared here
    9 | *main(int i, int dup, int signal) {
      |  ^~~~

我怀疑编译器在从前的工作方式下,即使在特定情况下main被定义为3个参数,你也可以只使用1个参数调用它。

这个说法准确吗?我有什么遗漏吗?现在使这段代码能够编译的最小更改是什么?

我使用了GCC 9.2.0和Makefile中建议的构建命令。

提前感谢,如果我忽略了什么非常明显的问题,请谅解!


1
1984年是我高中毕业的时候,也是第一个C标准发布前的5年。现在我们有了C11标准-§5.1.2.2.1程序启动(p1)。另请参阅:C和C++中main()应该返回什么? 因此,您的代码需要进行调整。 - David C. Rankin
你说得对,它可以被称为那样。那时候也没有原型。参数也可以声明为与现在预期的不同的类型。你没有错过任何明显的东西,因为那是一个不同的时代。那时候的事情远远不同,所以请不要因此感到难过。实际上,对于你无法理解的任何IOCCC条目,请不要感到难过!至于最少更改,你可能会发现我的答案有帮助(虽然不是我的工作,但我认识做这项工作的人)。如果你还没有看过,请参见这里 - Pryftan
虽然您作为 OP(原始发帖人)无疑会看到这条回复,但我还是想再次强调:您做得很好!您已经接近成功了。我只是发布了一个简单的修复方法,不需要任何特殊标志或其他东西。请参见 https://dev59.com/Ar3pa4cB1Zd3GeqPfoES#75929927。 - Pryftan
4个回答

6

简而言之:你的错误是给main函数提供了一个ANSI C原型(即将i更改为int i等),这会让编译器检查它被调用时的参数,并导致出现too few arguments错误。

例如:

echo 'int foo(a,b){return a+b;} int main(){return foo(3);}' |
  cc -Wall -std=c89 -xc -
# K&R C function OK, no errors

echo 'int foo(int a, int b){return a+b;} int main(){return foo(3);}' |
  cc -Wall -std=c89 -xc -
...
<stdin>:1:54: error: too few arguments to function ‘foo’

那段代码应该使用传统的 C 预处理器进行预处理,而不是使用“ANSI C”预处理器。使用标准预处理器会导致一些失真,例如 `<<=` 会变成 `<< =`,`*=` 会变成 `* =` 等等。
cpp -traditional-cpp -P decot.c > decot1.c

在添加正确的函数声明和强制类型转换后--请参见此答案末尾的差异和结果--你将得到一个在c89中仅有一个警告(在c99中有几个)的编译版本,并且像描述的那样,向标准输出打印一些垃圾内容:

$ cc -std=c89 decot1.c -lm -o decot1
decot1.c: In function ‘main’:
decot1.c:13:33: warning: function called through a non-compatible type
    (printf(&*"'\",x); /*\n\\", (*((int(*)())&floor))(i)));
                                ~^~~~~~~~~~~~~~~~~~~~
$ ./decot1
'",x);  /*
\

当我在V7 Unix上编译和运行原始代码时,所得到的结果与这完全相同,因此它应该是正确的 ;-)

decot1.c

double floor(double);
double sin(double);
int printf(const char*, ...);
int b,
k['a'] = {sizeof(
    int(*)())
,};
struct tag{int x0,*xO;}

*main(i, dup, signal) {
{
  for(signal=0;*k *= * "decot.c" *i;) do {
   (printf(&*"'\",x);   /*\n\\", (*((int(*)())&floor))(i)));
        goto _0;

_O: while (!(k['a'] <<= - dup)) {
        static struct tag u ={4};
  }
}


while(b = 3, i); {
k['a'] = b,i;
  _0:if(b&&k+
  (int)(sin(signal)             / *    ((main) (b)-> xO)));}}}

diff

$ diff -u decot1.c~ decot1.c
--- decot1.c~
+++ decot1.c
@@ -1,4 +1,6 @@
-extern int floor;
+double floor(double);
+double sin(double);
+int printf(const char*, ...);
 int b,
 k['a'] = {sizeof(
     int(*)())
@@ -20,4 +22,4 @@
 while(b = 3, i); {
 k['a'] = b,i;
   _0:if(b&&k+
-  sin(signal)          / *    ((main) (b)-> xO));}}}
+  (int)(sin(signal)            / *    ((main) (b)-> xO)));}}}

回答不错,但实际上比这简单得多。可以看看我的回答。虽然这并不重要,但对于那些想要更简单的方式的人来说还是有用的。 - Pryftan
顺便提一下:hint.text文件将来会被淘汰。实际上,它们已经从正在开发的网站中删除了。此时链接将无法使用。它可能最终会变成index.html,但是hint.text文件本身将变成README.md - Pryftan

5
原始代码已经无法编译。GCC声称支持的最早的C标准是C89,而来自这个比赛的代码是在它之前的时期写的。你已经很好地将其移植到了更现代的编译器上。但是剩下的问题不仅仅是main()的参数数量:
  • Clang和GCC都知道sin()返回一个double,即使你没有#include <math.h>,并且拒绝将double加到子表达式k+sin(signal)中的int *中。TCC似乎可以接受。
  • 变量fl00r被声明但未定义,链接器会抱怨找不到对它的引用。

请注意,通过避免在x的组合中使用<<x(预处理器现在只替换完整的记号,<<x算作一个记号),并使用三个参数调用main(),原始代码可以由TCC编译。

通过移除#include语句,回到使用floor(),但是像下面这样进行前向声明,你就可以使用GCC编译你的代码:

floor();

为了避免有关 sin() 的投诉,请使用 -fno-builtin 编译器标志。然后通过像 (main) (b,b,b) 这样调用 main() 来解决问题。

1
C89是迄今为止最早的C标准。在此之前,你有很多变体的语言,由Kernighan和Ritchie描述,它们大多彼此兼容。我不确定IOCCC在1984年对于提交作品需要与哪些编译器或目标平台兼容的规定是什么,但是了解一些比赛的精神,我可以想象围绕代码如何在不同的C实现中以不同的方式工作或根本无法工作的愚弄可能是其中的一部分乐趣。 - John Bollinger
1
@JohnBollinger K&R C 被认为是事实上的标准,它早于 C89。编译器有轻微的差异是正常的,即使在今天使用更新、官方的标准时也会发生。但是,其中一部分乐趣就是发现人们能从他们的帽子里拿出什么样的技巧。他们可能没有预料到某些技巧,这些技巧并不完全符合 IOCCC 的精神,例如 http://www.ioccc.org/1984/mullender/mullender.c,它只是注入了 PDP-11 机器代码。相关:我们有 https://codegolf.stackexchange.com/,在那里我们继续利用我们的编译器 :) - G. Sliepen
@G.Sliepen Re 他们可能没有预料到某些技巧,这些技巧并不完全符合IOCCC的精神:确实,他们不会预料到某些技巧,但那个出色的条目当时并没有违反精神。针对机器相关代码的限制并不在1984年的规则中。尽管你在其他方面是正确的。顺便说一句,如果你想知道,Landon是我的好朋友,但这也在README中提到了。 - Pryftan
1
关于你的答案,实际上要比这简单得多。请参见 我的回答 - Pryftan
顺便说一句,我认为你解决了这个问题很不错。虽然我并没有花费太多精力尝试,但我是 IOCCC 的三次获奖者,而我却没能让它正常工作。当然,我已经知道它已经被修复了,所以我没有太大的动力去想办法让它在现代系统上正常工作(使条目正常工作)——而且我只是简单地看了一下——但是,这确实是一项出色的工作。 - Pryftan
当然,我必须尝试并解决它。我将创建一个新答案,这可能是最干净和最简单的答案,不需要编译器或其他任何特殊标志。 - Pryftan

3
是的,它可以被编译...但不完全是原样。您还需要C预处理器选项-traditional-cpp,而clang不支持该选项。您可能还需要-m32(也许只适用于32位系统...在我的64位系统上,它无法以这种方式编译),但我不确定。
请注意,即使在 macOS 中看起来像是gcc,实际上gcc实际上是clang,因此除非您安装了真正的gcc编译器,否则它在macOS下不能像这样编译。 IOCCC冠军(指获胜条目最多的人)Yusuke Endoh已经写了关于1984年至2020年之间所有的参赛作品的文章(最初是用日语写的)(包括我的参赛作品),并且为那些可以编译的作品提供了补丁和编译技巧(并非所有作品都可以编译)。
顺便说一下,我们正在努力让早期的作品在更现代化的系统上编译。(不,我不是评委,但我正在与评委合作,Landon是我的好朋友)。我链接的网站已经不是最新的了,但它仍然是“官方”的。
关于 1984/decot:先应用此处的补丁,然后编译为:
gcc -traditional-cpp -o decot decot.c -lm

写作在此链接:https://mame-github-io.translate.goog/ioccc-ja-spoilers/1984/decot.html?_x_tr_sl=auto&_x_tr_tl=en&_x_tr_hl=en-US&_x_tr_pto=wapp

--

示例运行:
$ patch < 1984-decot.patch 
patching file decot.c
$ $ gcc -traditional-cpp decot.c -lm
decot.c:6:12: warning: built-in function 'floor' declared as non-function [-Wbuiltin-declaration-mismatch]
    6 | extern int floor;
      |            ^~~~~
decot.c: In function 'main':
decot.c:13:2: warning: type of 'i' defaults to 'int' [-Wimplicit-int]
   13 | *main(i, dup, signal) {
      |  ^~~~
decot.c:13:2: warning: type of 'dup' defaults to 'int' [-Wimplicit-int]
decot.c:13:2: warning: type of 'signal' defaults to 'int' [-Wimplicit-int]
decot.c:16:5: warning: implicit declaration of function 'printf' [-Wimplicit-function-declaration]
   16 |    (printf(&*"'\",x);   /*\n\\", (*((double(tag,u)(*)())&floor))(i)));
      |     ^~~~~~
decot.c:1:1: note: include '<stdio.h>' or provide a declaration of 'printf'
  +++ |+#include <stdio.h>
    1 | #define x =
decot.c:16:5: warning: incompatible implicit declaration of built-in function 'printf' [-Wbuiltin-declaration-mismatch]
   16 |    (printf(&*"'\",x);   /*\n\\", (*((double(tag,u)(*)())&floor))(i)));
      |     ^~~~~~
decot.c:16:5: note: include '<stdio.h>' or provide a declaration of 'printf'
decot.c:27:12: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
   27 |   _0:if(b&&(int)k+
      |            ^
decot.c:28:3: warning: implicit declaration of function 'sin' [-Wimplicit-function-declaration]
   28 |   sin(signal)           / *    ((main) (b)-> xO));/*}
      |   ^~~
decot.c:1:1: note: include '<math.h>' or provide a declaration of 'sin'
  +++ |+#include <math.h>
    1 | #define x =
decot.c:28:3: warning: incompatible implicit declaration of built-in function 'sin' [-Wbuiltin-declaration-mismatch]
   28 |   sin(signal)           / *    ((main) (b)-> xO));/*}
      |   ^~~
decot.c:28:3: note: include '<math.h>' or provide a declaration of 'sin'
$ ./a.out 
'",x);  /*
\$

1
很好,我不知道-traditional-cpp。有了你的补丁和这个标志,我成功在64位系统上编译它,而不需要-m32,但是我添加了-fPIC以确保它可以链接到符号floor - G. Sliepen
@G.Sliepen 请理解这不是我的补丁。在 Yusuke 写了关于它的文章之前,我也不知道那个选项。使用 -m32 似乎不起作用。我不确定他是否使用了 32 位系统,但在 64 位系统下似乎不起作用。正如所指出的,在 macOS 上默认编译器是 clang 而不是 gcc,即使你将其运行为 gcc,它也不起作用。 - Pryftan
2
GCC 12正在为x86-64编译:https://godbolt.org/z/ozxvrbnxr - G. Sliepen

1

我正在与评委们一起修复条目等问题。我已经为现代时代修复了很多条目。我刚刚修复了这个。实际上很简单。宏是有问题的,但你处理得很好。稍后我会给出一些提示。

完整的修复版本不需要任何特殊的编译标志(除了一些系统-lm),如下所示:

int b,
k['a'] = {sizeof(
    int(*)())
,};
struct tag{int x0,*xO;};

int dup, signal;
*main(int i) {
{
  for(signal=0;*k *= * __FILE__ *i;) do {
   (printf(&*"'\",x);   /*\n\\", (*((int(*)())&floor))(i)));
        goto _0;

_O: while (!(k['a'] <<= - dup)) {       /*/*\*/
        static struct tag u = {4};
  }
}


while(b = 3, i); {
k['a'] = b,i;
  _0:if(b&&(int)k+
sin(signal)              / *    ((main) (((struct tag *)b)-> xO)));/*}
  ;      
}         
         
*/}}}   

你可能需要在编译器中添加-include stdio.h -include math.h,或者将这些包含文件添加到代码本身中,但仅此而已。

就是这样简单。

编译

我们可以禁用更多警告,因为不同的编译器有不同的警告。这些与clang和gcc兼容:

cc -std=gnu90 -Wall -Wextra -Wno-array-bounds -Wno-error -Wno-implicit-function-declaration -Wno-keyword-macro -Wno-main-return-type -Wno-missing-field-initializers -Wno-unused-value -Wno-unused-variable   -include stdio.h -include math.h -O3 decot.c -o decot -lm

运行它

$ ./decot 
'",x);  /*
\

提示

  • 您不需要重命名floor。只需删除extern int floor;
  • 将main()原型更改为仅接受一个参数 - int。这是非标准的,但它可以与clang和gcc一起使用。
  • 您可能会发现需要对b进行强制类型转换,以便在调用main()时进行解引用,就像我上面所做的那样。
  • 您在宏替换方面做得很好。
  • 关于main()只有一个arg。 signaldup怎么了?嗯,在旧日子里,它们会默认为int,因此它们现在是代码中的全局变量。 但是为什么?好吧,试着改变main()并看看你遇到了什么问题!事实上,你已经做到了。修复这个问题将导致我提到的强制类型转换的问题。

就是这样。

你做得很好!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接