透过Redis看待我们是否应该继续使用c语言
杰克-逊の黑豹,恰饭了啦 []( ̄▽ ̄)
keywords: Redis
c
cpp
厌弃c语言的现象
长久以来,程序员们对待c语言的态度非常矛盾,主要应该有这么几种:
- 喜爱、工作中使用c语言;
- 不反感、工作中使用c语言;
- 不反感、工作中不使用c语言;
- 反感、工作中使用c语言;
- 反感、碰都不碰c语言;
赞许c语言的程序员,可能会有这些观点:
- 使用c语言的程序员是最nb的;
- 真正的编程大佬都使用c语言;
讨厌c语言的程序员,大抵是因为这些:
- 没有舒服的包管理机制;
- 没有丰富的标准库;
- 没有面向对象的语料支持;
- 内存不够安全(内存泄漏、悬垂指针、非法指针、无效指针等等);
- 编译器输出信息不够好;
- 错误处理不够舒服;
- 项目管理复杂;
再加上,像Rust/Go/Swift等这种现代编程语言的出现,更让很多程序员厌弃c语言。
现代编程语言和c语言比起来,有什么最大的特点?以我个人而言,最大的特点就是编程语言的表达能力
非常出色,不是C语言可以比拟的。表达能力更具体地讲就是更灵活、更模块化、更适合人阅读。
难道说c语言真的是魔鬼吗?
反驳者一定会举出嵌入式开发、操作系统开发、驱动开发领域的成功案例。
但我觉得,这样就又大又空洞了。
近日,我在看Redis1.3.6源码,觉得倒是个很好的例子说说这事儿,不过读者大可放心,本文不是xx源码阅读
, xx源码细品
的文章,更不是什么八股文,单纯作为一个例子,解释下c语言是不是魔鬼这回事儿。
先说下我个人的结论吧。
c语言是不是魔鬼,取决于你能hold住多少c语言代码量
。hold住,c语言的缺点不是事儿;hold不住,c语言就会带来悲剧。一定程度上,这就像我们尝试三个小时保持精神高度集中一样,诚然你说自己的c语言功夫非常扎实,但是随着项目规模增大,我可不相信你照样可以注意到每个角落的c语言隐患。
如果你是95后,c语言不适合作为你的主力开发语言,但是你可以学习由c语言开发且非常稳定的项目,也可以基于c语言ABI将这些c语言项目植入到别的编程语言开发的项目中,比如Rust/Go/Javascript/Swift项目。
为什么选择Redis1.3.6
事物总是由简入繁,功能总是由少渐多,项目总是由骨到肉。
越早的版本,越容易看出作者的宏观思路。
越新的版本,越会充斥各种新功能、小功能,越发遮掩作者最初的构思。
我从github下载好Redis后,发现最早的一个版本号就是1.3.6, 所以就用这个做参考了。
实际上,我硬着头皮看了两天最新的版本,发现确实啃不动。
看看Redis怎么解决包管理问题的
在1.3.6版本中,redis没有依赖的包,但是在最新版本中,redis将这些依赖放置于deps文件夹下。这里边没有什么特别的地方,就是将依赖的仓库源码放置在一个单独的文件夹下,定期去看看仓库源码有没有更新,如果更新的话,按需要将本地做更新,就和你的本地仓库、远程仓库一样。
同时,你也看到了,依赖并不多确实可以这样做,如果依赖多的话,这么搞就难搞了,就需要包管理程序(第三方的或者自己开发的)。不过嘛,一般用c语言开发的项目,都是追求极度性能,功能非常专一的项目,不会掺入太多的依赖。但是应用层项目就不同了,依赖贼多。
看看Redis怎么解决标准库不太丰富的
c语言开发时用到的API基本上是操作系统原生提供的API,这些API都分布在操作系统提供的固定的.h
文件中,比如:
unistd.h
sys/sysctl.h
pthread.h
sys/stat.h
fcntl.h
execinfo.h
ucontext.h
当然也需要c语言提供的标准库.h
文件,比如:
stdlib.h
stdio.h
stdarg.h
erron.h
ctype.h
例如redis.c
文件中就用到了很多耳熟能详的.h
文件:
两类之外的功能,要么从第三方拷贝,要么就要自己写。当然,在redis1.3.6中依赖并不多,只需要一些数据结构的实现,比如哈希表(hash table)、动态字符串(dynamic string)、双向链表(double linked list)、压缩型字符串映射表(zip map, 应该是redis作者独创的)。这些都是redis作者实现的。
可不要被实现
吓坏了。
自己创造式地想到一个数据结构,然后用代码写出来,叫做实现;
自己参考数据结构书籍、论文中的理论,给出自己的实现,这也叫做实现。
都成年人了,要知道:考试不是只叫做闭卷考试
。
像pqsort(部分快速排序),redis作者就是参考NetBSD平台下的libc源码实现的,作者在代码注释中给出了声明:
你可能会问了,我实现的版本性能无法保证怎么办?redis作者其实也告诉我们答案了,那就是先搞出一版实现,至于性能优劣是另一个问题,可以先不用管它。于是,在ae.c
的文件中,我们看到redis作者写到这样的注释:
c项目一般保持功能专一、性能卓越,常常要定制化一些数据结构的代码,所以即便标准库加入这些数据结构,也未必能满足c项目的需要,可能也派不上多大用场。
如此看来,c项目不太需要那种普适、统一的标准库,更需团队开发、企业开发、组织开发范围内的标准库。
但是对于技术经验尚浅(没查阅论文、手册、其余资料,独立实现一些功能库)的开发者来说,他们需要的是涵盖功能的标准库,而不是刚提到的那种高度定制化的标准库。
所以标准库不太丰富的这种问题,对于项目不大、开发者具备经验的情况而言,不成问题;对于经验不足的开发而言,是个头大的问题;对于项目贼大的情况而言,无论经验多少,都是头大的问题。
看看Redis是怎么解决面向对象编程的
面向对象编程,说的就是一种编程思想,但一部分开发者常常会被编程语言的形式所蒙蔽,认为编程语言给出了面向对象代码形式(提供class extends public等关键字),才算面向对象了,对于没有给出这种形式的编程语言,就无法面向对象编程。
看看Redis是怎么做的吧。
struct
等效于class
。aeBeforeSleepProc
就是类中定义的成员方法啊,其定义如下:
至于继承,可以使用组合
的方式替代;
至于多态,可以继续使用函数指针或者改用函数映射表替代;
宽泛地讲,只要有了封装
, 即便没有给出严格的继承
和多态
, 也可以认为是面向对象
。毕竟,面向对象是一种编程思想,不是僵死的格式。
不过呢,语言本身如果提供了面向对象概念的关键字,代码直接理解起来的难度就大大降低,加之有IDE功能的帮助,读代码更加顺畅,这是c代码所不及的。这也就是说,当项目的概念变得非常多,c代码即便可以面向对象编程,但也是个问题,会让读代码的人一通找啊找。
Redis怎么解决内存安全问题的
这个问题确实是要害,在这个版本中,redis并没有给出什么非常好的内存安全手段,有的话,也只是在定义数据结构的时候,给出该数据结构的内存释放方法:
和cpp的析构函数一个道理,只不过cpp是编译器插入内存释放代码,c语言是开发者手动加入。
在代码量hold的住的情况下,开发者确实有足够的精力和耐心确保内存问题,但是代码量一旦上涨,开发者恐怕就顾不过来了。你可以看到像linux这样大型的项目有不少issue,但你很少听说hello-world级别的c代码有什么issue。
所以,到底是人去处理内存问题还是编译器处理内存问题,归根结底就是在大量工作的条件下,你更相信人的表现还是机器的表现。相比之下,我觉得机器更靠谱一些。
c项目很复杂,无法阅读?
一般而言,在同样成熟的情况下,c项目的代码读起来会麻烦一些。但这是成熟之后,也就是在加入杂七杂八功能、BUG布丁等等之后的c项目。以Redis 1.3.6为例,其结构相当简洁:
主体框架就这么直白,后续版本的复杂化,都是功能扩展、结构调整,这条主线是不可能断掉的。所以,别把C项目想象的那么复杂,那么难。C项目的复杂还有一种可能是C语言表达能力导致的。因为C语言语法足够简洁,直接用的操作系统API,封装比较少,别的语言三言两语交代的事情,C语言可能要多说很多话才可以表述出来,但是编译到了二进制形式的产物,C语言就比别的语言“简洁”了。
后话
C语言并没有那么可怕,它不是开发者的定时炸弹,也不是开发者的无双宝剑。C语言到底怎么样,很大程度上要看代码量和开发者的承受能力。并不是说很多项目由C语言编写,就证明C语言是一门你可以信赖并使用的语言,很大程度上讲,很多项目用C语言开发是历史问题,在那个年代和阶段,编程语言的选择余地太少,哼不能从1980年等到2010年以后,再使用现代化编程语言编写代码吧?
从另一个角度上看,这个时代你完全可以不使用C语言,没必要跟着上古大佬的品味走。须知,工具是用来解放你的生产力的,不是给你的生产力添堵的。你用不用C语言,和你是不是一个合格的软件工程师,没什么必要的联系。
不过,不用归不用,学习还是要学习的,毕竟ABI层面还是以C语言为标准的,想理解函数调用、指令跳转等内容,C语言是避不开的。
对于要不要继续使用C语言,你有什么看法呢,欢迎留言。
来源:juejin.cn/post/7236354905493176377