问题描述
这段令人着迷的代码在国际混淆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 signal) {
| ^~~~
我怀疑编译器在过去的工作方式意味着您可以以某种方式仅用1个参数来调用main,即使在这种情况下它是用3专门定义的。
这样准确吗?我想念什么吗?如今,使该代码编译所需的最少更改是什么?
我在Makefile中使用了GCC 9.2.0和建议的build命令。
如果我错过了很明显的事情,请先谢谢您的歉意!
解决方法
tl; dr; ,您的错误是为ANSI C原型提供了main
函数(即,将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
在添加了正确的函数声明并添加了强制类型转换之后(请参见本答案末尾的diff&结果),您将在c89中获得一个带有单个警告的编译内容(在c99中有多个警告),并且,as described,将一些垃圾输出到stdout:
$ 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)));}}}
,
您不能再按原样编译原始代码。 GCC声称支持的最早的C标准是C89,该竞赛的代码来自于此。在将其移植到更现代的编译器上,您已经做得很好。但是,剩下的问题不仅仅是main()
的参数数目:
- Clang和GCC都知道
sin()
返回double
,即使您没有#include <math.h>
,也拒绝将double
添加到{{1 }}在子表达式int *
中。 TCC似乎接受了。 - 已声明但未定义变量
k+sin(signal)
,链接器将抱怨未定义的引用。
请注意,可以通过避免使用fl00r
之类的组合(例如x
)来由TCC编译原始代码(如今,预处理器仅替换完整的令牌,而<<x
被视为一个令牌),并通过三个参数调用<<x
。
您的代码版本可以由GCC删除main()
语句,然后再回到使用#include
的方式进行编译,但可以这样声明:
floor()
为避免抱怨floor();
,请使用sin()
编译器标志。然后通过调用-fno-builtin
来解决main()
的问题。