复杂的“Hello World!“

今天在网上看到一个输出 hello world!C代码,如下:

#include <stdio.h>

int main(int _)
{
    (_ ^ 13) && main(-(~_));
    putchar(_["~)' #%$&($##!\""][" !$(+.3\335\334\306"-32]+68);
    return 0;
}

测试后确实可以正确输出 hello world!

但是为什么呢?让我们来慢慢剖析。

首先看到 main() 有一个参数 _,其实这个 _ 只是一个变量名,要是看不习惯,完全可以改为其他普通变量名称,这里我们就改为最常用的 argc 吧。

关于 main() 函数参数的说明,可以参考这个连接(是不是震惊于main还要第三个参数?)。

修改变量名后,代码如下:

#include <stdio.h>

int main(int argc)
{
    (argc ^ 13) && main(-(~argc));
    putchar(argc["~)' #%$&($##!\""][" !$(+.3\335\334\306"-32]+68);
    return 0;
}

而在不带参数运行 main() 函数时,argc 的值为 1,表示有一个参数,即“文件名”。
接下来看 (argc^13) && main(-(~argc));
首先我们需要知道这是一个“与”运算,而“与”运算有一个很重要的特征,那就是“短路”
那就是说只有前面的 (argc^13) 为真的情况下,才会执行后面的 main(-(~argc))
那么 (argc^13) 什么时候为真呢?这是一个“按位异或”运算,当且仅当 argc 也等于 13 时,这个表达式才为假。也就是说,只要 argc 不是 13,就会执行“与运算”后面的表达式 main(-(~argc))
那么 main(-(~argc)) 又干了什么呢?很明显,这是一个函数调用,只是这里调用的是 main() 函数,即递归调用,而且这里还带了一个参数。那这个参数计算完后是多少呢?看下表:

argc(~argc)-(~argc)
1-22
2-33
3-44

发现规律了吗?
argc = 1时,main(-(~argc)) 就相当于 main(2);
argc = 2时,main(-(~argc)) 就相当于 main(3);

那么当 argc = 13 时,会发生什么呢?这时候 argc ^ 13 为假,并不会调用 main() ,而是往下执行了!
接下来,终于可以总结第一个语句 (_ ^ 13) && main(-(~_)); 的功能了,即不断调用 main(argc) 函数,直到 argc = 13。既然这样,我是否可以修改一下这个代码,让其更好理解呢?
如下,这个代码和上面的代码执行结果是一样的:

#include <stdio.h>

int cnt = 1;

int main(int argc)
{
    while(cnt < 13) {
        main(++cnt);
    }
    putchar(argc["~)' #%$&($##!\""][" !$(+.3\335\334\306"-32]+68);
    return 0;
}

看到这里,你能猜到 13 这个数字的意义了吗?没错,就是 hello world! 这个字符串的长度(包括了换行符\n)。
main() 函数中,递归调用 main() 的那个语句本质上只是调用了一个函数,并没有执行功能;main() 函数的功能,都是由 putchar() 函数完成的。
我们知道,putchar() 函数每次只能输出一个字符,所有要想输出 hello world!\n 就得运行 13putchar()
上面的代码,我们可以继续作如下简化:

int main(void)
{
    putchar(13["~)' #%$&($##!\""][" !$(+.3\335\334\306"-32]+68);		// 'h'
    putchar(12["~)' #%$&($##!\""][" !$(+.3\335\334\306"-32]+68);		// 'e'
    putchar(11["~)' #%$&($##!\""][" !$(+.3\335\334\306"-32]+68);		// 'l'
    putchar(10["~)' #%$&($##!\""][" !$(+.3\335\334\306"-32]+68);		// 'l'
    putchar(9["~)' #%$&($##!\""][" !$(+.3\335\334\306"-32]+68);			// 'o'
    putchar(8["~)' #%$&($##!\""][" !$(+.3\335\334\306"-32]+68);			// ' '
    putchar(7["~)' #%$&($##!\""][" !$(+.3\335\334\306"-32]+68);			// 'w'
    putchar(6["~)' #%$&($##!\""][" !$(+.3\335\334\306"-32]+68);			// 'o'
    putchar(5["~)' #%$&($##!\""][" !$(+.3\335\334\306"-32]+68);			// 'r'
    putchar(4["~)' #%$&($##!\""][" !$(+.3\335\334\306"-32]+68);			// 'l'
    putchar(3["~)' #%$&($##!\""][" !$(+.3\335\334\306"-32]+68);			// 'd'
    putchar(2["~)' #%$&($##!\""][" !$(+.3\335\334\306"-32]+68);			// '!'
    putchar(1["~)' #%$&($##!\""][" !$(+.3\335\334\306"-32]+68);			// '\n'
    
    return 0;
}

到这里,就完全把递归调用 main() 函数的步骤全部去掉了,只需要分析出
putchar(13["~)' #%$&($##!\""][" !$(+.3\335\334\306"-32]+68); 这一个语句就可以了。
分析这个语句之前,我们先看一个简单一点的代码:

#include <stdio.h>

int main(void)
{
	int a[3] = {1, 2, 3};
	
	printf("1[a] = %d\n", 1[a]);				// 1[a] = 2
    printf("1[a-1] = %d\n", 1[a-1]);			// 1[a-1] = 1
    printf("2[a-2] = %d\n", 2[a-2]);			// 2[a-2] = 1
    printf("2[a-3] = %d\n", 2[a-3]);			// 一个随机值
    printf("a[-1] = %d\n", a[-1]);				// 一个随机值,但是一定和上面的随机值相等
    
	return 0;
}

亲,发现规律了吗?这里面的关键点就是“数组名”表示的意义是这个数组首个元素的地址,即 a = &a[0];
知道这个知识点后,我们再接着来分析 putchar() 语句,我们给它加上一个括号,使各个运算符的优先级更明显:
putchar((13["~)' #%$&($##!\""])[" !$(+.3\335\334\306"-32]+68);
那么这里面的 13["~)' #%$&($##!\""] 是个什么鬼呢?根据前面的分析很快可以将其转换成如下代码:

#include <stdio.h>

int main(void)
{
	char str[] = "~)' #%$&($##!\"";
	
	putchar(str[13]);							// "
	putchar(13["~)' #%$&($##!\""]);				// "
	
	return 0;
}

字符在 ASCII 码中也就是一个数字, " 对应的十进制数就是 34
所以 putchar((13["~)' #%$&($##!\""])[" !$(+.3\335\334\306"-32]+68); 可以再次简化成
putchar(34[" !$(+.3\335\334\306"-32]+68);
到这里后是不是已经轻车熟路了?再次把 34[" !$(+.3\335\334\306"-32] 简化后就成了:

#include <stdio.h>

int main(void)
{
	char str[] = " !$(+.3\335\334\306";
	
	putchar(str[34-32]);							// $
	putchar(34[" !$(+.3\335\334\306"-32]);			// $
	
	return 0;
}

在 ASCII 表中,$ 对应的十进制数是 36,那么 putchar(36+68) 的输出是什么呢?当然是 h 啦,因为 h 的ASCII 码刚刚好就是 104,是不是很巧?
要是再把其余的 putchar() 语句也分析一遍,会发现惊人的巧合,输出的就是 hello world!\n 这个字符串,神奇吧~
(废话,本来就是经过巧妙设计的~~)

那么,为什么下面的值是这样呢?

#include <stdio.h>

int main(void)
{
	printf("%d\n", '\335');		// -35
	printf("%d\n", '\334');		// -36
	printf("%d\n", '\306');		// -58
	return 0;
}

这时候就该我们win10自带的计算器上场啦:
在这里插入图片描述 在这里插入图片描述\306
这下知道为啥了吧!

是不是以为自己都懂了?那来看看这个?

// 输出编译这个程序时的系统时间
main(_)
{
    _^448 && main(-~_);
    putchar(--_%64?32|-~(7[(__TIME__)-_/8%8])[">'txiZ^(~z?"-48]>>";;;====~$::199"[_*2&8|_/64]/(_&2?1:8)%8&1:10);
}

输出如下图所示:
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值