大软件的烦恼与编译技术

转自http://www.lingcc.com/2012/01/30/11974/

现在虽说卖硬件的比不上卖软件的,卖软件的比不上卖服务的。但软件仍然是服务的基石。
而且不管是软件、硬件、还是服务,背后都是一行行的代码,以及基于这些代码所形成的软件功能、硬件系统、技术人员的经验等等。
这些代码有的用C/C++、Java、C#写,有的用PHP、JavaScript、Ruby写,有的用Verilog写。

这些软件往往是公司的看家本领。比如Oracle的 Oracle数据库,Synopsis的EDA工具、Adobe的Photoshop、微软的Windows、Visual Studio等。
软件做的强了,难免要用到许许多多的平台上;用的人多了,又难免会有不停的修修补补。
对于靠编程手艺挣钱养家的各位朋友,相信绝大多数都调过遗产代码中的bug。
也大概都生过“代码太烂、文档太少、逻辑太混乱”的怨念。

年前曾和几位朋友交流了一下基于持续开发好多年的大软件(代码量30多G)上的开发、移植和debug问题。
这篇文章试图基于这次交流,结合自己的经验和认识,给出一些利用编译技术解决相关问题的方式。
虽然自己也是在大软件上开发,但因为尚未走出校园,所接触的开发也偏重小作坊的方式,了解和应用的手段还非常少,敬请各位不吝指正。

Table of Contents

  • 1 为什么会有这些问题
    • 1.1 代码层次上的问题
  • 2 编译技术能作什么
    • 2.1 精准的代码定位
    • 2.2 减小编译时间
      • 2.2.1 并行编译
      • 2.2.2 头文件搜索
    • 2.3 相关学术研究
      • 2.3.1 注释检错和纠正
      • 2.3.2 丰富的程序警告输出
      • 2.3.3 Patch中的问题
  • 3 参考

1 为什么会有这些问题/h2>

一个大的软件项目,往往需要许多年的持续开发。虽然开始时,结构、逻辑、模块化都设计的非常好,号称各种情况都考虑到了。
项目文档写了一摞又一摞,但随着开发和测试的进行。都会有不少的bug冒出来,慢慢的缝缝补补越来越多。
原本设计的清清楚楚的逻辑就慢慢在源代码中不那么清晰了,代码的逻辑越来越复杂。

虽然一开始留下详细的设计和说明文档是完全可行的,但接下来的bug修补多数只能留下一些bug report。
要想把软件项目文档和 bug report整合在一起,形成持续更新、体系完整的详细文档,可不是那么容易的事情。
再加上我们在刚刚接触这类代码的时候,面对的是一个一无所知的世界,而看到的一行行代码许多都是针对细致末节的,计算机该如何如何一步步完成任务的详细说明,难懂和怨念也就不奇怪了。

1.1 代码层次上的问题

对于草根软件工程师,咱只能从代码上说了。
大软件意味着巨大的代码量,一般都是上千万行级的代码。
在这茫茫代码中,寻找你此刻想了解的某一段代码的定义、引用和与之相关的所有注释谈何容易/p>

幸好,有Source Insight、Ctags之类的工具,能让我们稍微找到一点点脉络。
不过当面对Linux、GCC这种跨平台软件时,这些工具也难免会找错函数、变量和类型的定义和引用位置。

另外,软件太大,编译时间也会很长,这也会降低开发速度。
虽然Makefile可以用-jN的方式,多进程同时编译。
不过大软件中,也不都能像GCC、Linux Kernel这样顺利使用-jN编译下来。
至少《编译点滴》博主在使用Gentoo系统的5年多时间里,出现过不少次make -j3有问题,但重新emerge一下就过的情况。
这种情况除了用依赖处理不好来解释外,我还没想到别的。

层层包含的头文件,也让问题的定位越来越难hold。
尤其是某些头文件中的定义又被一堆ifdef的宏扩在一起的时候。
乱七八糟的逻辑关系让人头疼。
虽然编译器可以提供预处理功能,适当的帮我们做些宏展开和完全不相干代码的删除工作。
但巨大的预处理文件,我们也只能从文件最后慢慢的往前找。

要是再来点刺激的并发支持,什么多线程、锁、共享变量。
靠!就能砸了显示器,跑去指着老板的鼻子说:什么破代码,爷不干了。

2 编译技术能作什么

其实,铺垫了这么多,就是一句话,编译技术其实能帮上忙。

编译器想必大家最熟悉的功能就是能将源代码转换成可执行文件。
不过编译器能做的不仅仅是这些。
对于常见的编译型命令式语言(C/C++),编译器理论上可以知道程序的所有行为。
之所以加”编译型、命令式“的定语是因为有些语言却是编译器没办法在编译时刻完全知道所有的行为。
比如 JavaScript语言可以通过eval(string)函数,在程序运行的时刻,重新组织一个Javascript语句的字符串,然后通过调用eval函数执行它。
这个字符串,有可能和输入相关。所以编译器无法在很早的时候知道它的行为。
之所以加上“理论上”,是因为某些分析工作状态空间太大,搜索复杂度太高,没办法在短时间内完成。
比如过程间分析、指针分析之类的代码分析技术,面对上百万、上千万的代码时,可不是一时半会儿能出结果的。

咱们不能只是说说问题有多难就算了,幸好我们已经有了不少的解决方法。
《编译点滴》了解了一些将编译技术应用于协助解决这些问题的领域。
用上编译技术的程序员,会男的更帅、女的更漂亮,早日找到另一半,还能调节心情、预防老年痴呆、避免2012人类灭亡。

2.1 精准的代码定位

这个可能是在阅读源代码时,最最实用的了。
但目前能做到对于大规模软件实用的,除了商业的source insight用于C/C++,其他的《编译点滴》还没怎么听说,欢迎各位朋友建议。

即便是强大的Source Insight,也还要看具体的代码情况。
比如对Open64这种包含不少ifdef宏的代码,即使是source insight,也要再做些针对性的设置才能hold住。
eclipse的CDT基本搞不定(本博主对Eclipse的了解有限,尝试了几次,都不好使,只好放弃)。
ctags也找的很乱,许多时候结果也不靠谱。

但这类代码分析工具就真的无能为力了吗然编译器都能编译得到可执行文件。可执行文件能找到正确的函数和变量定义。
那么代码分析工具就完全没有理由做不到这些。stackoverflow有篇文章(参考链接4),对比了若干常用代码阅读工具对待600M以上代码量时的情况.

现在纯粹利用编译器,产生tags信息提供给代码阅读软件使用的工具也有一些。比如GCC MELT、LLVM的libclang。
另外还有些代码库自带了tags生成机制,比如GCC编译时,执行“make tags”命令,就能生成emacs下可以使用的tags信息。
LLVM则一直都通过Doxygen,解析源码中的注释,自动生成代码文档。
这些tags信息的生成背后,都是编译前端的相关理论。

2.2 减小编译时间

代码基太大,编译的时间自然就会很长。修改一个头文件或者Makefile,想看看效果如何,就得重新编译几个小时,这个速度谁都受不了。
于是如何减小编译时间,就是个比较大的问题。

2.2.1 并行编译

来源:mobilechan

声明:本站部分文章及图片转载于互联网,内容版权归原作者所有,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2012年3月17日
下一篇 2012年3月17日

相关推荐