黑客与C语言

“黑客”这个词想必我们已经如雷贯耳了。我们一听到黑客通常在大脑中的印象就是一群穿着黑衣,躲在小屋里偷偷用着数台电脑针对某组计算机,神不知鬼不觉地进行攻击。他们通常会攻入一些网络或系统,潜伏在一些大型网站,窥探甚至窃取用户隐私,比如窃取你的QQ号、微信号、你的邮箱,诸如此类的事情。他们通常拥有高超的技术,于无形中做很多我们或惊叹或惊吓的事。他们就像《海盗船》的Jack船长一样分明是“恶势力”,却又诡异、神秘,有超强的能力,而好莱坞中各类电影和电视剧对黑客的渲染更是使我们对他们的世界充满了探究的意愿。

其实黑客有时候也是统称,也有灰客、白客。然而,以上这只是狭义上的黑客,其实在现在的英语中用Cracker来描述这种专门搞计算机系统以及网络系统破坏的人。而广义上讲,黑客(Hacker)对于程序员而言其实是指精通于计算机以及计算机网络的人。所以这么一来我们就能理解为何许多伟大的系统缔造者、编程语言缔造者能被称为黑客了,尽管他们并不是以破坏系统而闻名。

1. Unix系统创始人Dennis Ritchie

这里首先介绍的就是大名鼎鼎的Dennis Ritchie,于2011年12月逝世。他是伟大的Unix系统的创始人,同时也是著名经典的C编程语言的缔造者。曾在1983年从ACM获得图灵奖。在早些时候,Unix系统其实是用汇编语言开发的,那个时候Dennis Ritchie与另一个伟大的黑客Ken Thompson(现就职于Google,并打造了Go语言)在贝尔实验室一起实现了在DEC生产的PDP-7计算机上的Unix系统。那时,他俩准备将此操作系统移植到PDP-11上。刚开始,PDP-11上的Unix系统仍然是用汇编语言开发的,但是因为PDP-11与PDP-7的变化还是有不少的,所以那时候开发者打算用B语言来重写该系统。B语言是由Ken Thompson从BCPL编程语言简化而来的。然而,B语言无法很好利用PDP-11上的某些特性,比如字节寻址,这就使得Dennis Ritchie与Ken Thompson一起打造了更灵活、更强大的C编程语言。而C语言一开始也就是针对PDP-11计算机上的Unix系统而打造的。在1972年,Unix中的大部分代码都用C语言重写。到1973年,引入了结构体类型 struct 之后,C语言就基本成型了,因为它足够强大,所以足以担当Unix系统内核大部分功能的实现。而此时的C语言也被称作为“K&R C”。

当然,Dennis Ritchie也有他调皮的一面。在早期开发的Unix系统中,他特意留了一些后门。其他开发者用自己账号登录系统之后,他们发现自己的文件或某些资料被改动过,一直很纳闷。他们后来通过排查,发现了当时Unix系统的一个漏洞,把它堵上后,但没过多久自己的账号又被侵入了。后来才知道,原来是Dennis Ritchie在C语言编译器上埋下了后门,所以只要他们用编译器编一次程序,那么漏洞就会自动生成,哈哈……这个也让笔者联想起前两年很多iOS开发者通过百度网盘下载带有后门的Xcode,使得很多App受到木马侵袭,而且该木马能躲过Apple的代码审核机制。尽管该漏洞破坏性不大,因为iOS系统以及iOS设备处理器的本身运行安全机制很厉害,不过这也说明了来自编译器的后门是防不胜防的。

2. Linux系统内核缔造者Linus Torvalds

下面说的这位黑客应该大家非常熟悉了,就是大名鼎鼎的Linus Torvalds,Linux系统内核的缔造者,Git版本管理工具的缔造者。Linus Torvalds从1988到1996年在自己祖国芬兰的赫尔辛基大学修完了硕士学位。在此期间,他看了Andrew Tanenbaum的一本书《Operating Systems: Design and Implementation》,在此书中Andrew描述的是MINIX系统,该系统是Unix剥离下来的一个用于教学的版本。此时,由于在芬兰很难获得软件,所以这也促成了Linus Torvalds喜欢自己动手的习惯,他购买了一套Sinclair QL,然后自己为它写了一套汇编器以及编辑器,然后自己独立编写了一个类似吃豆人(Pac-Man)的小游戏,称为Cool Man。在1991年,他购买了基于Intel 80386的IBM PC,同时在此之前也收到了MINIX的一个拷贝,从而他就开始了在Intel 80386上的Linux内核开发。Linux的第一个版本正式版本1.0在1994年3月14号发布。在2005年,Linus创建了Git这一版本控制系统(VCS)的开源项目,基于GPLv2许可证。现在我们看到很多项目、工具以及网站都会默认使用Git工具进行版本控制,包括Xcode,GitHub等等。

当然,Linus Torvalds跟不少程序员一样,也有偏执、狂傲的一面。比如在开发Git项目过程中,有位开发者表示Git项目用的都是纯C语言而不是C++表示不可理解,而且也直言不讳:“别拿可移植性说事,那是屁话”。并且还指出,蛮力地直接操作文本,既啰嗦又易错,而且很难跟上高层代码逻辑。当时Linus Torvalds对此发出了强烈的不满!他一上来也爆粗口——“YOU * are full of bull shit”,紧接着他开始炮轰C++了,哈哈。大致意思如下:

C++是一种可怕的语言。而且因为有大量不够标准的程序员在使用而使情况更糟,以至于极容易产生彻头彻尾的垃圾(total and utter crap)。老实说,选择C就是为了把C++程序员踢出去。……我有这样的结论,任何喜欢用C++而不是C开发项目的程序员可能都是我希望踢出去的人,免得他们来搞乱我参与的项目。C++会导致非常非常糟糕的设计选择。你们这些C++程序员总是一上来就用语言的那些’漂亮的’库特性比如STL、Boost和其他彻头彻尾的垃圾,这可能对你们的程序有所‘帮助’,但是却会导致: (1)当库无法工作时无穷无尽的折磨(别跟我说什么STL尤其是Boost很稳定而且可移植性很好,那全是屁话,而且一点都不可笑) (2)低效的抽象编程模型,可能在两年之后你会注意到有些抽象效果不怎么样,但是所有代码已经依赖于围绕它设计的‘漂亮’对象模型了,如果不重写应用程序,就无法改正。

呵呵,其实Linus说得也确实没错。对于一些系统级项目,使用C++甚至更高级的编程语言可能反而会使整个项目难以维护,因为当代码量上升的时候,很多设计需要围绕原有的设计模型进行。这就好比洗衣机的滚筒,洗衣的次数多了,滚筒上就会慢慢沾满各种碎衣料,然后越滚越多。C语言的设计理念就是专注于功能模块,而不是以某个特定的设计模型为中心展开堆码,这也是C语言的灵活性所在。

以上我们谈到的是Hacker这个词,他是一个名词,用于表示某类人。而我们日常所说的黑客还能做动词使用,其实也就是对应英语中Hack这个单词。Hack本意是劈、砍、乱踢这类意思,因而当它用于计算机系统时就是指对系统进行猛烈攻击,从而找到其漏洞。现在由于Hack的使用范围又广了,它还能用于编程语言。像Apple在2014年推出Swift编程语言时就称它为Hackable programming language。这里的Hackable就是说该编程语言是可用来做各种另类玩法的,在现有语法体系中能玩出令人意想不到的效果,写出惊世骇俗的代码来。而C语言也是Hackable的。因为它灵活、强大,不死板,所以我们很多时候可以用C语言的语法糖实现各种相当不错的API封装以及功能实现。我这里举两个简单的例子。

像我们用C语言在开发一套程序时,有时为了调试方便会自己定义一个用于打印输出日志的接口,在调试模式将它开启,在发布模式将它屏蔽。对于遵循C99的编译器,我们通常会这么定义:

#ifdef DEBUG
#define DEBUG_LOG(...)  (void)printf(__VA_ARGS__)
#else
#define DEBUG_LOG(...)  (void)0
#endif

而对于不遵循C99标准的C语言编译器,并且不能使用不定参数个数的宏定义时我们如何定义呢?我们初步能想到的是以下这种方式:

#ifdef DEBUG
#define DEBUG_LOG   (void)printf
#else
#define DEBUG_LOG   (void)
#endif

这种定义方式基本没什么问题。不过当我们碰到以下这种代码时,这种定义方式在发布模式下的行为会与前面C99模式的有所不同。

    int a = 10;
    DEBUG_LOG("a = %d\n", ++a);

我们请注意看,这里在DEBUG_LOG中对所要打印的变量a进行了递增操作,从而使得变量a在调用DEBUG_LOG之前产生了副作用。依照C99那种定义方式,在发布模式下,由于整个DEBUG_LOG(…)这个宏被定义为了 (void)0,所以这里面的表达式都不会被扩展出来,因此++a这个表达式是不存在于源代码中的。而在上面C90模式下的实现方式由于没有屏蔽++a这个表达式,从而会使它产生副作用。那如果我们想在发布模式下与C99那种形式一样屏蔽掉DEBUG_LOG宏中所有表达式的副作用该如何实现呢?其实非常简单!在C90中就已经有了编译时行为的操作符——sizeof!sizeof操作符内的表达式在C90中是不会产生任何副作用的。同时,由于对于打印函数来说,必定存在一个表示字符串的表达式,因此我们也无需担心传入的表达式是否可能为void表达式。所以我们可以在发布模式下这么定义DENUG_LOG:

#define DEBUG_LOG   (void)sizeof

非常神奇吧~正因为C语言有各种形式的类型、操作符、预处理器的存在,所以我们可以用它实现多种功能,从而达到自己期望的效果。

我们再举一个例子,是关于联合体的。联合体是个好东东!它一般用于抽象数据类型,从而可使得该数据类型在某一特定场合是一种类型,再另一种特定场合又是另一种类型,但是所占的存储空间是其中最大的成员那个。所以它既能做到类型抽象的作用,同时也能缩减存储空间,对于一些系统开发而言很有帮助。我们下面想定义一个联合体类型的常量数组,但是该常量数组中每个元素的数据类型可能是不同的。在遵循C99标准的代码中我们可能会这么写:

static const union MyData
{
    int i;
    float f;

}sc_list[] = {
    {.i = 10},
    {.f = 0.5f},
    {.i = 20}
};

如果是在不遵循C99标准的C语言中该如何表示呢?我们这里能看到,第二个元素是直接对f单精度浮点类型的成员进行初始化,所以如果我们直接写0.5f那由于其实元素是int类型,编译器仍然会将它转为int类型,从而变为整数0。其实如果我们知道在当前C语言环境中倘若单精度浮点用的是遵循IEEE754规格化浮点数表示的话,那么我们可以直接用IEEE754对二进制浮点数的表示来做替换。这里,0.5在IEEE754标准中32位单精度浮点的表示为0x3F000000,所以我们可以用以下代码进行替代:

static const union MyData
{
    int i;
    float f;

}sc_list[] = {
    10,
    0x3F000000,
    20
};

这就是一种Hack方法。感谢各位能看完此贴,本贴主要讨论了关于黑客的一些科普介绍,并且没有针对计算机与网络系统攻击做详细介绍。因为此类文章其实也已经有不少了,这里提供一篇比较不错的关于缓存溢出攻击的文章,讲得比较详细:http://www.cnblogs.com/fanzhidongyzby/archive/2013/08/10/3250405.html

此外,如果大家想了解Linus Torvalds“炮轰”C++更详细的信息,可见此贴:http://os.51cto.com/art/200709/55562.htm。

C语言最为一门更接近硬件底层的高级编程语言具有非常良好的抽象力、表达力和灵活性。此外,它具有非常高效的运行时性能。所以C语言从1970年直到现在都作为系统级编程的首要编程语言。C语言博大精深,其思想也奠定了后续众多语言的设计基础,Linux/Unix、Windows、PHP、Redis、Android内核等你耳熟能详的系统、语言或者软件都是基于C,可以说“无C语言,不编程”。

原文发布于微信公众号 - CSDN技术头条(CSDN_Tech)

原文发表时间:2017-06-29

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏牛客网

51一面面经

13600
来自专栏封碎

两个最容易被人忽略的基本代码优化技术 博客分类: 经典文章转载 算法Android编程D语言工作

      本文转载自http://sd.csdn.net/a/20100921/279732.html

9830
来自专栏点滴积累

geotrellis使用(四十二)将 Shp 文件转为 GeoJson

原因很多,最重要的原因是我转行了。是的,我离开了开发岗位,走向了开发的天敌-产品经理。虽然名义上是产品经理,但是干的事情也很杂,除了不写代码,其他的都干,经常还...

20620
来自专栏程序员的诗和远方

20181028_ARTS_week18

比较简单的题目,第一反应直接可以用自带的 indexOf 之类的去解决。除此之外就可以用个 while 循环一个个比一下就搞定了。

10730
来自专栏逍遥剑客的游戏开发

星际2中复刻DOTA白虎

19720
来自专栏小樱的经验随笔

CTF---密码学入门第五题 传统知识+古典密码

传统知识+古典密码分值:10 来源: 霜羽 难度:易 参与人数:2297人 Get Flag:735人 答题人数:938人 解题通过率:78% 小明某一天收到一...

419120
来自专栏Golang语言社区

【Go 语言社区】使用 Redis 实现排行榜功能

排行榜功能是一个很普遍的需求。使用 Redis 中有序集合的特性来实现排行榜是又好又快的选择。 一般排行榜都是有实效性的,比如“用户积分榜”。如果没有实效性一直...

548100
来自专栏包子铺里聊IT

DAG、Workflow 系统设计、Airflow 与开源的那些事儿

DAG (Directed Acyclic Graph) 是一个非常有用、也有很有意思的数据结构。如果说数组、链表、二叉树这类数据结构是学习中的基础,那么 DA...

58640
来自专栏非典型技术宅

iOS传感器:利用磁力计完成一个AR场景应用1. 磁力计的介绍2. 磁力计的使用3. 开始我们的小案例

20240
来自专栏令仔很忙

UML总结

   描述类与类之间的使用与被使用关系,而其使用关系具有偶然性的、临时性的、非常弱的,但是被使用的一方会影响到使用的一方,比如说:“动物”和“氧气”,动物的生活...

10910

扫码关注云+社区

领取腾讯云代金券