让人迷惑的C代码


chapter 0:

之前有人开玩笑说码农工资按代码行数来算的,现在有个办法让你瞬间变成富富富!

先写几行代码:“a.c”


#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>

int main(){
	return 0;
}



如果每行代码1毛钱,写完这几行代码买包辣条都不够,不过别担心,神器登场:


gcc a.c -save-temps



运行上面的命令(微软VS工具也可以),看看多了几个文件,其中有个特别的“a.i”文件。编译这个文件和编译“a.c”是完全等价的。用文本编辑器打开a.i看看,一万六千多行。提交代码的时候,把a.i重命名为a.c交上去,笑。



chapter 1: 混乱C

国际C语言混乱大赛每届的比赛都有许多精品代码,让人看了之后抓狂。

1、前处理器命令(Preprocessing Symbol Definition)

代码文件:a.c


A



你没看错,源码就只有一个字符:‘A’。不过在编译的时候需要输入特别的命令:



gcc a.c -save-temps -DA=main(){printf(\"HelloWorld!\");}



这利用的宏定义,我们将A定义为一个宏,当GCC处理源码时,会将A替换为对应的宏。



2、Makefile和C混合

这个可能看起来有些奇怪,这一个代码文件既是源码又是Makefile,所以可以用


mingw32-make -f tomx.c



来编译这个文件。


来自:IOCCC 2000 tomx


#include <stdio.h>
#define  true

true /*:all

CC=cc
PROG=tomx

false :
	make -f $0 $1
	exit 0

all: $(PROG)

%:%.c
	$(CC) $< -o $@

clean:
	rm $(PROG)

.PHONY: /* true clean */
	int main() {return!printf("Hello, world\n");}

其中C的注释以“/*”开头的话只会在第一次遇到“*/”时结束。这样makefile的脚本就隐藏到了注释里面,而make程序解释的时候这个代码刚好符合makefile的语法。



3、滥用C的转移符号和内置符号等

来自:IOCCC 1998 schweikh2


#define _POSIX_SOURCE /* you are not supposed to understand this */
#define O(OOO,OO0,O00,O0O,OO,O0)\0##O0O%:%:OO\0##O0%:%:OOO\0##OO0%:%:O00
#include<signal.h>
static volatile sig_atomic_t One;
#include<stdlib.h>
#include<unistd.h>
#define Zero(NULL)#NULL
#define ONE(One) Zero(One)
in??/
t
#line 10 "01\015"
main (register zero, char **ONE)
%:
<% switch (sizeof __FILE__ < zero) case 1: return One /= zero;
{ auto one = zero = atoi (ONE<:zero-1:>);
do for (one += alarm (One |= (signal (__LINE__, (void(*)(register))main)
!= SIG_ERR)); One; ++one);
while (zero -= write (1, __FILE__+(one&1), 1));
return write (1, 1+__FILE__+1, 1) != 1;
%>}



上面的代码如果仅是很了解C的语法不了解C的历史的话看起来应该是满头雾水。这里使用了很早以前的一些特殊转义符号,也有内置宏,另外还有一些预处理指令。举个例子:



// Will the next line be executed????????????????/
 a++;

 /??/
 * A comment *??/
 /



上面两行源码都是注释。两行?对,就是两行,在预处理器眼中,这看起来5行的源码只有两行而已,这涉及到预处理器内部实现的问题了,有兴趣可以看看 预处理器的源码。解释一下,这里的“??/”等价于“\”,上面的源码其实是(虽然这么写有些不合适):



// Will the next line be executed??????????????a++;

 /* A comment */



这里的#开头的预处理指令和__FILE__之类的内置宏就忽略了,另外的特殊转义符号叫做三目式和二目式(翻译过来这么应该差不多,笑:),更多详细的信息可以到维基百科搜索“Digraphs and trigraphs”。在使用GCC编译这样的代码时需要使用 -trigraphs 和 -digraphs指明说我要使用这些转义,不然会识别不了。这些转义符的使用是正确的,不过上面的代码确实也导致了我的mingw崩溃,应该是版本兼容的问题,我没仔细看上面的代码(偷懒了:)。



4、语法

来自:IOCCC 1990 baruch


v,i,j,k,l,s,a[99];
main()
{
	for(scanf("%d",&s);*a-s;v=a[j*=v]-a[i],k=i<s,j+=(v=j<s&&(!k&&!!printf(2+"\n\n%c"-(!l<<!j)," #Q"[l^v?(l^j)&1:2])&&++l||a[i]<s&&v&&v-i+j&&v+i-j))&&!(l%=s),v||(i==j?a[i+=k]=0:++a[i])>=s*k&&++a[--i])
		;
}



GCC可以编译通过,“v,i,j,k,l,s,a[99]”这些据说是默认当成int类型了。


5、C的特殊语法

来自:IOCCC 1986 stein


typedef char*z;O;o;_=33303285;main(b,Z)z Z;{b=(b>=0||(main(b+1,Z+1),*Z=O%(o=(_%
25))+'0',O/=o,_/=25))&&(b<1||(O=time(&b)%0250600,main(~5,*(z*)Z),write(1,*(z*)Z
,9)));}



太难看了,对其一下:



typedef char*z;
O;
o;
_=33303285;

main(b,Z)
z Z;
{
	b=
		(b>=0
		||
		(main(b+1,Z+1),
		*Z=O%(o=(_%25))+'0',
		O/=o,
		_/=25))
	&&
		(b<1
		||
		(O=time(&b)%0250600,
		main(~5,*(z*)Z),
		write(1,*(z*)Z,9)));
}



ok,看过前面的一定直接就发现main函数是忽略返回类型了,“_”被当成变量了。但是“z Z;”是怎么回事,这是C和C++的一处不同,C可以这么写函数的:



int main(argc, argv)
        int argc,
        char **argv;
{
        return 0;
}




6、看不清的符号

来自:IOCCC 1985 lycklama


#define o define
#o ___o write
#o ooo (unsigned)
#o o_o_ 1
#o _o_ char
#o _oo goto
#o _oo_ read
#o o_o for
#o o_ main
#o o__ if
#o oo_ 0
#o _o(_,__,___)(void)___o(_,__,ooo(___))
#o __o (o_o_<<((o_o_<<(o_o_<<o_o_))+(o_o_<<o_o_)))+(o_o_<<(o_o_<<(o_o_<<o_o_)))
o_(){_o_ _=oo_,__,___,____[__o];_oo ______;_____:___=__o-o_o_; _______:
_o(o_o_,____,__=(_-o_o_<___?_-o_o_:___));o_o(;__;_o(o_o_,"\b",o_o_),__--);
_o(o_o_," ",o_o_);o__(--___)_oo _______;_o(o_o_,"\n",o_o_);______:o__(_=_oo_(
oo_,____,__o))_oo _____;}



上面的代码有些人看到肯定会觉得眼熟,对这个的解释到 这里来看,我也是因为看了 这个才发现有混乱代码这种东西的。



7、直接写二进制代码

来自:IOCCC 1984 mullender


short main[] = {
	277, 04735, -4129, 25, 0, 477, 1019, 0xbef, 0, 12800,
	-113, 21119, 0x52d7, -1006, -7151, 0, 0x4bc, 020004,
	14880, 10541, 2056, 04010, 4548, 3044, -6716, 0x9,
	4407, 6, 5568, 1, -30460, 0, 0x9, 5570, 512, -30419,
	0x7e82, 0760, 6, 0, 4, 02400, 15, 0, 4, 1280, 4, 0,
	4, 0, 0, 0, 0x8, 0, 4, 0, ',', 0, 12, 0, 4, 0, '#',
	0, 020, 0, 4, 0, 30, 0, 026, 0, 0x6176, 120, 25712,
	'p', 072163, 'r', 29303, 29801, 'e'
};



没写过汇编代码或者对汇编了解不多的人可能看不懂这是什么东东。这个某种意义上已经不能算是C代码了,因为这个实际上是把一个编译好的HelloWorld的obj文件写成一个数组。



8、语法

来自:IOCCC 1987 korn


main() { printf(&unix["\021%six\012\0"],(unix)["have"]+"fun"-0x60);}



这个代码编译的编译器应该是内部宏定义的unix,所以应该在代码中加“#define unix 1”。具体这是怎么回事请看 这里



9、语法

来自:原创


#include<stdio.h>
int lastYear (int l)
	{if(!l)printf("Year!\n"); return l;}
	
int (*thisYear(char *t)) (int l)
	{printf("%s", t); return lastYear;}
	
int (*(*nextYear(double n))(char *t)) (int l)
	{printf("Happy ");return thisYear;}


int main(argc, argv)int argc; char **argv;{return (nextYear(0.001)("New ")(0));}


这份代码的主要问题在于“函数类型”上。函数的返回值可以是一个整数,一个指针,也可以是一个函数指针。





转载于:https://my.oschina.net/hanjianqiao/blog/374502

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值