关于知乎话题“程序员有哪些借口可以让自己写出低质量的代码?”的一点想法

以下是知乎上一个有趣话题:“程序员有哪些借口可以让自己写出低质量的代码?”得票最多的回答(原帖请参考这里:http://www.zhihu.com/question/24665029):


关于这件事,网上有很多讨论,感兴趣的同学可以搜一下。我感兴趣的一点是:“离职之前老板大夸我厚道,最后一个月还给公司做了这么多的事情,别人走都是删代码,我居然还毫无保留的为公司做出贡献。”。我理解这个公司离职的员工删代码应该不是一个正常的代码维护,而是为了报复公司去删除有用的代码,否则老板也不会这样说。我好奇这个公司的员工为什么离职都会有报复公司的举动,我也经历了几家公司,但从来没有听说过这种行为。我没在这家公司工作过,所以不知道具体原因是什么。是不是所有经历过这家公司的人都应该反思一下,为什么会出现这种情况呢?也许答案并不简单。

编译gdb注意事项:一定要用一个干净的build文件夹

上周收到gdb的通知邮件,说gdb 7.8已经release了。作为gdb的忠实用户,自然是体验一下了。我赶紧下了一个版本,然后build了一下,结果在最后link阶段,出了错误:

gcc: unrecognized option `-rdynamic’
ld: warning: file /usr/local/lib/libiconv.so: attempted multiple inclusion of file
Undefined                       first referenced
 symbol                             in file
observer_attach_exited              cli-interp.o
observer_notify_signal_exited       infrun.o
observer_attach_sync_execution_done cli-interp.o
observer_attach_command_error       cli-interp.o
observer_notify_signal_received     infrun.o
observer_attach_end_stepping_range  cli-interp.o
observer_attach_no_history          cli-interp.o
ptid_match                          remote.o
observer_notify_end_stepping_range  infrun.o
observer_notify_exited              infrun.o
observer_notify_no_history          infrun.o
observer_attach_signal_exited       cli-interp.o
observer_notify_command_error       event-loop.o
observer_notify_sync_execution_done infrun.o
observer_attach_signal_received     cli-interp.o
ld: fatal: symbol referencing errors. No output written to gdb
collect2: ld returned 1 exit status
Makefile:1334: recipe for target ‘gdb’ failed
make[2]: *** [gdb] Error 1
make[2]: Leaving directory ‘/export/home/nan/build_gdb/gdb’
Makefile:8667: recipe for target ‘all-gdb’ failed
make[1]: *** [all-gdb] Error 2
make[1]: Leaving directory ‘/export/home/nan/build_gdb’
Makefile:831: recipe for target ‘all’ failed
make: *** [all] Error 2

gdb的编译过程一向是最简单的,link错误我还是第一次碰到。我先是在gdb的IRC里讨论,接着又自己debug,检查代码文件和脚本,结果最后得到的结论是:因为我所用的build目录之前是编译7.7.1版本的,我因为偷懒,没有清空,导致现在编译7.8版本时会有问题,原因应该是link的还是7.7.1的目标文件。

最后得到的经验是:在编译gdb(或是其它软件)时,一定要准备一个干净的build文件夹,否则可能会有你意想不到的问题,像我碰到的在link阶段出问题还好办,如果在执行阶段出了问题,可就更难查了。

注:build文件夹是为了保持源码的干净,通常在源代码文件夹外,新建一个build文件夹,然后所有的configure,make操作等都在这个build文件夹下进行。例如:

mkdir build_gdb;
cd build_gdb;
../gdb-7.8/configure
make
make install

 

在Solaris上编译redis(2.8.13)

最近由于工作需要,又要做redis相关的工作。正好今天早上redis发布了最新的2.8.13版本,于是就下载了最新的版本并试着在Solaris上编译了一下,还是发现了一些问题。于是在此记录下来,说不定对其他人会有帮助。

(1)Solaris 9

我先选了一台Solaris 9,结果一开始编译就遇到“IPV6_V6ONLY”未定义错误。上网查了一下,发现Solaris不支持这个socket选项。我改了一点代码,绕过去这个问题,结果再编译遇到了没有stdint.h文件这个错误。于是果断决定放弃Solaris 9,转投Solaris 10。

(2)Solaris 10

Solaris 10上的编译过程还是比较顺利的,但是链接时出了两个问题:

a)nanosleep函数没有定义:Solaris上调用nanosleep,需要链接“rt library”,即在Makefile里加上“-lrt”。我把这个patch报给了redis(https://github.com/NanXiao/redis/commit/232d77938add77d95c91a714462389e62e2bf126),不过不确定redis会不会采纳。

b)isinf没有定义,需要在调用make命令时传入MISSING_ISINF宏定义,即“make “CFLAGS+=-DMISSING_ISINF” ”。

Source Insight的“Expand tabs”选项介绍

Source Insight有个“Expand tabs”选项(Options->Document Options->Editing Options->Expand tabs),其功能是把Tab展开成空格:比方说,你的Source Insight的设置是1个Tab占4个空格的宽度,那么当你勾选“Expand tabs”选项,代码里相应的一个Tab就变成四个空格了。这个选项在下面的情形会有用:

比如说Github上某个项目的缩进都是用4个空格,而Github的Tab默认又是8个空格。我本地的Source Insight设置又是一个Tab占4个空格的宽度。这样我本地代码采用Tab缩进,显示是对齐的(一个Tab占4个空格的宽度),但是check in Github上,就发现代码会多缩进(Github的Tab默认是8个空格)。这时如果我把“Expand tabs”选项选上,所有的Tab都变成了空格,这样check in以后就是4个空格,显示代码就是对齐的了。

如何写gdb命令脚本

作为UNIX/Linux下使用广泛的调试器,gdb不仅提供了丰富的命令,还引入了对脚本的支持:一种是对已存在的脚本语言支持,比如python,用户可以直接书写python脚本,由gdb调用python解释器执行;另一种是命令脚本(command file),用户可以在脚本中书写gdb已经提供的或者自定义的gdb命令,再由gdb执行。在这篇文章里,我会介绍一下如何写gdb的命令脚本。

(一) 自定义命令
gdb支持用户自定义命令,格式是:

define commandName  
    statement  
    ......  
end  

其中statement可以是任意gdb命令。此外自定义命令还支持最多10个输入参数:$arg0$arg1 …… $arg9,并且还用$argc来标明一共传入了多少参数。

下面结合一个简单的C程序(test.c),来介绍如何写自定义命令:

#include <stdio.h>

int global = 0;

int fun_1(void)
{
    return 1;
}

int fun_a(void)
{
    int a = 0;
    printf("%d\n", a);
}

int main(void)
{
    fun_a();
    return 0;
}

首先编译成可执行文件:

gcc -g -o test test.c

接着用gdb进行调试:

[root@linux:~]$ gdb test
GNU gdb (GDB) 7.6
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-unknown-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /data2/home/nanxiao/test...done.
(gdb) b fun_a
Breakpoint 1 at 0x4004d7: file test.c, line 12.
(gdb) r
Starting program: /data2/home/nanxiao/test

Breakpoint 1, fun_a () at test.c:12
12              int a = 0;
(gdb) bt
#0  fun_a () at test.c:12
#1  0x0000000000400500 in main () at test.c:18

可以看到使用btbacktrace缩写)命令可以打印当前线程的调用栈。我们的第一个自定义命令就是也实现一个backtrace功能:

define mybacktrace
    bt
end

怎么样?简单吧,纯粹复用gdb提供的命令。下面来验证一下:

(gdb) define mybacktrace
Type commands for definition of "mybacktrace".
End with a line saying just "end".
>bt
>end
(gdb) mybacktrace
#0  fun_a () at test.c:12
#1  0x0000000000400500 in main () at test.c:18

功能完全正确!

接下来定义一个赋值命令,把第二个参数的值赋给第一个参数:

define myassign
    set var $arg0 = $arg1
end

执行一下:

(gdb) define myassign
Type commands for definition of "myassign".
End with a line saying just "end".
>set var $arg0 = $arg1
>end
(gdb) myassign global 3
(gdb) p global
$1 = 3

可以看到global变量的值变成了3

对于自定义命令来说,传进来的参数只是进行简单的文本替换,所以你可以传入赋值的表达式,甚至是函数调用:

(gdb) myassign global fun_1()
(gdb) p global
$2 = 1

可以看到global变量的值变成了1

除此以外,还可以为自定义命令写帮助文档,也就是执行help命令时打印出的信息:

document myassign
    assign the second parameter value to the first parameter
end

执行help命令:

(gdb) document myassign
Type documentation for "myassign".
End with a line saying just "end".
>assign the second parameter value to the first parameter
>end
(gdb) help myassign
assign the second parameter value to the first parameter

可以看到打印出了myassign的帮助信息。

(二) 命令脚本
首先对于命令脚本的命名,其实gdb没有什么特殊要求,只要文件名不是gdb支持的其它脚本语言的文件名就可以了(比如.py)。因为这样做会使gdb按照相应的脚本语言去解析命令脚本,结果自然是不对的。

其次为了帮助用户写出功能强大的脚本,gdb提供了如下的流程控制命令:
(1)条件命令:if...else...end。这个同其它语言中提供的if命令没什么区别,只是注意结尾的end
(2)循环命令:while...end。gdb同样提供了loop_breakloop_continue命令分别对应其它语言中的breakcontinue,另外同样注意结尾的end

另外gdb还提供了很多输出命令。比方说echo命令,如果仅仅是输出一段文本,echo命令特别方便。此外还有和C语言很相似的支持格式化输出的printf命令,等等。

脚本文件的注释也是以#开头的,这个同很多其它脚本语言都一样。

最后指出的是在gdb中执行脚本要使用source命令,例如:“source xxx.gdb”。

(三) 一个完整的例子

最后以一个完整的gdb脚本(search_byte.gdb)做例子,来总结一下本文提到的内容:

define search_byte
    if $argc != 3
        help search_byte
    else
        set $begin_addr = $arg0
        set $end_addr = $arg1

        while $begin_addr <= $end_addr
            if *((unsigned char*)$begin_addr) == $arg2
                printf "Find it!The address is 0x%x\n", $begin_addr
                loop_break
            else
                set $begin_addr = $begin_addr + 1
            end
        end

        if $begin_addr > $end_addr
            printf "Can't find it!\n"
        end
    end
end

document search_byte
    search a specified byte value(0 ~ 255) during a memory
    usage: search_byte begin_addr end_addr byte
end

这个脚本定义了search_byte命令,目的是在一段指定内存查找一个值(unsigned char类型):需要输入内存的起始地址,结束地址和要找的值。
命令逻辑可以分成3个部分:
(a) 首先判断输入参数是不是3个,如果不是,就输出帮助信息;
(b) 从起始地址开始查找指定的值,如果找到,打印地址值并退出循环,否则把地址加1,继续查找;
(c) 如果在指定内存区域没有找到,打印提示信息。

另外这个脚本还定义了search_byte的帮助信息。

仍然以上面的C程序为例,来看一下如何使用这个gdb脚本:

[root@linux:~]$ gdb test
GNU gdb (GDB) 7.6
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-unknown-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /data2/home/nanxiao/test...done.
(gdb) p &global
$1 = (int *) 0x600900 <global>
(gdb) p global
$2 = 0
(gdb) source search_byte.gdb
(gdb) search_byte 0x600900 0x600903 0
Find it! The address is 0x600900
(gdb) search_byte 0x600900 0x600903 1
Can't find it!

可以看到global的值是0,起始地址是0x600900,结束地址是0x600903。在global的内存区域查找0成功,查找1失败。

受篇幅所限,本文只是对gdb命令脚本做了一个粗浅的介绍,旨在起到抛砖引玉的效果。如果大家想更深入地了解这部分知识,可以参考gdb手册的相关章节:Extending GDB (https://sourceware.org/gdb/onlinedocs/gdb/Extending-GDB.html)。最后向大家推荐一个github上的.gdbinit文件:https://github.com/gdbinit/Gdbinit/blob/master/gdbinit,把这个弄懂,相信gdb脚本文件就不在话下了。

参考文献:
(1)Extending GDB (https://sourceware.org/gdb/onlinedocs/gdb/Extending-GDB.html);
(2)捉虫日记(http://www.ituring.com.cn/book/909)。

 

 

UNIX/Linux C 程序员需要掌握的七种武器

我是一名普通的软件工程师,不是什么技术大牛。这篇文章所提到的“七种武器”只是我这些年工作经验的一点体会和感悟,如果有错误的地方,还请大家指正。

(一)C语言
作为一名C程序员,熟练掌握C语言是最基本的一项技能。关于如何学好C语言,以及C语言话题的讨论,网上有很多经典的文章,我就不一一列举了。在这里,我只想谈一点我个人的体会:刚毕业时,我来到一家比较大的软件公司工作,而公司的工作模式是每个人只负责一个小模块。这样工作两年后,我自认为我的C语言水平已经很高了。后来,我来到现在这家公司。由于工作需要,接触的东西也多了,开源项目也了解一些,才发现自己的C语言能力太一般了:原来宏可以这样写,指针可以这样用…等等。现在我在写代码时,常常会想:这个行为是C语言规范定义的吗?如果是,是C89还是C99?我现在用的编译器支持吗?如果不是C语言规范定义的,那么在程序运行的这个平台,行为是确定的吗?所以我建议大家平时可以多想想这些问题,查查资料,相信一定会对C语言有更深的理解。

(二)UNIX/Linux系统编程
在UNIX/Linux系统上开发程序,掌握系统编程API是必不可少的技能。而这方面的经典书籍都是一些大部头的英文著作,让人望而生畏。在这里,我谈一点自己的建议:我觉得可以先找一本口碑不错的中文书先看一下,了解一下都有哪些种类的API。这样以后用到时,再去精读经典英文著作里的相关章节,或是查man手册。此外,我觉得如果有时间,可以学习一下经典的开源项目,了解这些开源项目是如何使用这些API的。举个例子,Redis是很多人推荐的一个很不错的学习C语言的开源项目。我在阅读代码时,看到保存数据到文件时会用到fsync函数,我就会去深入地了解一下这个函数的作用。这样比单纯地去看那些著作效果要好。

(三)网络编程及相关知识
关于网络方面,我想从以下三点分享一下我的体会:
a)网络协议。在日常的工作中,大家接触和使用最多的无疑是TCP/IP协议族。此外,由于工作领域不同,也可能用到其它的协议。比方说,我主要做电信相关的程序开发,平时可能接触SCTP协议会更多一些。对于这些协议,我觉得掌握最基本的知识是必须的,其它的边边角角知识可以等到用时再查。举例来说,TCP协议的“三次握手”,“四次挥手”,“TIME-WAIT状态”这些基本的知识点要弄明白,其它的边角知识学完老不用忘得也快,还是用时google一下效率更高。
b)Socket编程。Socket编程的经典书籍一点不比讲系统编程的书薄,所以可以选一本相对薄点,口碑不错的精读一下,这样基本就掌握的百分之五、六十了。另外有时间还是看一下经典的开源代码。这里还拿Redis举例,Redis里关于处理网络连接和通信的代码量不大,而且基本涵盖了所有常见的UNIX平台,看完以后一定受益匪浅。
c)协议分析工具。TCPdump、snoop(Solaris平台工具)、wireshark等这些工具不仅能帮助我们抓取数据包,还能分析数据包,这对debug网络程序有非常大的帮助。所以,我们至少要掌握这些工具最常用的功能。此外,对于开放源代码的工具,我们更是可以从代码中学到很多知识。举例来说,我做短信相关的项目,就从wireshark的分析短信协议的代码里学到了很多东西,这使我对短信协议有了更清晰的理解。

(四)脚本编程能力
一提到脚本编程,大家首先想到的可能就是Bash shell脚本编程了。不错,在Unix/Linux上,Bash shell也许就是用的最广泛的脚本编程语言。我平时主要用Bash shell做两个方面的工作:a)用于编写监控服务脚本;b)写一些简单的单元测试脚本,比如循环发一些命令,等等。但是Bash shell的功能远远要比这些强大。我见过一些高手用Bash shell编程语言写出了很好玩的游戏,也有人做出了很cool的网络应用。所以建议大家有兴趣可以多了解一下Unix/Linux的这层“壳”。当然,你也可以选择学习Python、Perl、Ruby等。不过相比这些语言,Bash shell至少你不用自己去安装,而且它能做的事同样很强大。

(五)操作系统及CPU体系结构
也许有一天,你会碰到这样的情景:你的程序在Solaris上会发生core dump,在Linux上却运行的好好的。经过一番艰苦的debug,最后得到的原因是两种操作系统对线程的调度策略不一样,这会使一个对全局变量没有加锁就访问的bug在Linux上很难出现。所以你需要尽可能地去了解你使用的操作系统,这样无论对写程序还是debug都会有很大的帮助。比如,你需要了解进程的内存布局,这样你就知道栈和堆到底在内存的哪段空间,为什么内存写越界有时会core dump,有时没事。
除了操作系统,了解CPU的体系结构也是一门必修课。比方说,SPARC CPU要求字节对齐,而X86 CPU则没有这个要求。又比如SPARC CPU是大端模式,而X86 CPU是小端模式,这就要求你对像位域这样的定义要格外小心。你还要了解你使用的CPU的汇编语言,至少能大概看懂。因为有些时候,当你从C代码中找不出bug的原因时,就需要你“透过现象看本质”,从汇编代码层面看看到底发生了什么。

(六)编译器和调试器
“工欲善其事,必先利其器”。编译器负责把源代码生成可执行文件,而调试器则是在程序出现bug时,用来“降妖除魔”的不二神器。我会以我最熟悉的gcc和gdb为例子,来谈一下个人体会。
gcc有很多编译选项,除了要熟悉像-O,-g这些最基本的选项,我建议大家可以多了解一些其它不常见的选项。因为这些选项很可能帮助我们找到程序的一些bug。举个例子,在比较新的gcc版本里,增加了-fstack-protector这个选项,而它可以帮助我们检查到缓冲区溢出这种bug。此外,你还可能碰到这种情况,一个bug总是发生在程序优化后的版本,而不会出现在没经过优化的版本。所以,多了解你的编译器,你就可以更好地了解你的程序是如何生成的。
一个程序员不可能不碰到bug,而这个时候,调试器就是最好的工具。可以说,在遇到bug时调试技巧和手段是否丰富是衡量一个程序员的能力和水平的重要参考。除此以外,gdb另一个重要用途就是分析程序的core dump文件。我个人很喜欢看破案和推理的电视节目。我觉得程序的core dump文件可以比作“犯罪现场”,而gdb就是公安人员用来在现场提取线索的工具。对gdb越熟悉,就越能从core dump文件提取有价值的信息,也就越有助于我们定位到程序bug的“root cause”。

(七)DTrace/SystemTap
DTrace是由Sun的几位才华横溢的工程师开发的,最开始只支持Solaris操作系统,现在FreeBSD和Mac OS X也都支持了。Linux上类似的工具有SystemTap,也有人把DTrace移植到Linux上,不过效果似乎并不好。简单地说,DTrace可以几乎不会在对整个系统有任何性能影响下,让你了解你的程序所发生的一切。这对分析程序的热点(“Hot spot”),了解程序的执行流程,定位程序bug都有很大的帮助。有些时候,DTrace可能是你唯一的工具。举例来说,有个程序只发生在生产环境,而在实验室环境无法复现(当然,理论上任何bug都可以复现,只是我们没有找到复现条件。)。你不可能在你怀疑的代码打上断点,然后用gdb去调试。这时你只能借助于DTrace,通过它去了解程序到底是如何运行的,当时的变量值是什么。此外,DTrace还可以帮你了解操作系统的kernel,这会满足很多geek的好奇心。

以上就是我总结的UNIX/Linux C 程序员需要掌握的“七种武器”,同时也很感谢你能耐心地读完这篇文章。如果这篇小文没有让你觉得浪费时间,而是对你有哪怕一点小小的帮助,我的目的也就达到了。如果你有一些好的观点,也非常欢迎你能分享出来。谢谢!

分享几篇关于gcc和gdb的文章

海法Linux俱乐部是一群在以色列海法地区生活的Linux工程师定期组织的,分享在Linux下开发程序经验的聚会。网站是http://www.haifux.org/。其中有很多topic,涉及的范围很广,感兴趣的同学可以登陆这个网站看一下。我把其中和gcc和gdb相关的文章选出来,分享在下面:
gdb – customize it the way you want
Advanced GDB
Compiling Effectively for Cell with GCC
GCC Profile Guided Optimization

Source Insight的Rebuild project功能

当使用Source Insight时,有可能会出现有些定义的macro或function找不到(显示为黑),原因通常是source insight的数据库文件被破坏了,这时可以用“Rebuild Project”这个选项去重新生成数据库文件:选择“project->Rebuild Project->Re-Create the whole project from scratch”。详细介绍可参考:http://www.sourceinsight.com/docs35/ad920311.htm

Solaris搭建64位C语言开发环境

刚来公司时,公司的C程序还是32位的。后来我阅读了一些资料,觉得64位的程序才是真正的趋势,所以就开始尝试着开发64位的程序。这篇文章介绍如何在Solaris下搭建64位C语言开发环境,希望给需要的朋友一点帮助。

(1)gcc

Solaris的/usr/sfw/bin/gcc可以用来编译64位C程序,但是需要加-m64编译选项,否则默认编译出来的是32位程序。此外也可以从gcc的官网下载gcc源代码,自行编译安装,但是要注意编译出来的gcc需要是64位的。

(2)gdb

调试64位C程序需要64位的gdb,gdb的安装步骤如下(以7.6版本为例):

1)gunzip gdb-7.6.tar.gz
2)tar xvf gdb-7.6.tar
3)export CC=”/usr/sfw/bin/gcc -m64″
4)mkdir build_gdb
5)cd build_gdb
6)../gdb-7.6/configure –prefix=“/…/…(a folder path)”
7)make
8)make install

需要注意以下两点:

a)Solaris下的tar程序不支持”-z”选项,所以只能先调用gunzip,再调用tar,不能一步搞定:tar -xzf gdb-7.6.tar.gz。

b)目前gdb的最新版本是7.7,在Solaris下编译会有错误。解决办法也很简单,可以参考这篇文章

(3)参考资料

个人认为Oracle的这本《Solaris 64-bit Developer’s Guide》,是在Solaris下开发64位C程序最好的资料。每一位C语言开发者都应该看一下,相信都能受益匪浅。