发现写连载有个坏处:就是俺的水平还没有到能一次写好的地步~~所以有了新想法以后还得回头去改之前写的东西。因此如果你不是很急着看的话,最好等到俺完成整篇文章以后再看。
昨晚写到后面已经很困了,所以越写越少。今天回头在(1)的inline部份补了一些东西。下面继续讨论C99的扩展。
混合声明(mix declarations and code)
其实也就是解除了原先必须在block的第一条语句之前声明变量的限制:现在C99也和C++一样,可以在代码中随时声明变量了。
不过我看很多人尤其是初学者,可能压根不晓得原来C99之前是不能在代码里混着声明变量啊。。。因为大部分C编译器其实都扩展支持了这个特性(比较严格的也最多打个warning罢了),而N多菜鸟用C垒代码,都是觉得缺了个变量就顺手定义一下然后就继续往下垒了,能编译过就算胜利~~不过现在倒是好了。。如果有老师傅鄙视你随地声明变量,你可以理直气壮地告诉他:这是C99标准支持的!
其实混合声明对于编码的重要意义在于它对提高代码的可读性方面帮助很大。在C89里面,稍微复杂一点的函数中通常会看到这样的壮观景象:
int load_elf_file(const char *filename, int argc, int argv, int *retcode)
{
int elf_exec_fileno;
int i, retval;
uint k, elf_bss, elf_brk, elf_entry;
uint start_code, end_code, end_data;
struct elf_phdr *elf_ppnt, *elf_phdata;
struct elfhdr elf_ex;
struct file *file;
...;
}
代码还没开始就先被一堆变量砸晕,这个就是C89必须在Block第一条语句之前声明变量带来的后果~~不管一个变量是否一直到Block的最后一行才被用到,你也不得不在一开始就声明它。而根据某研究(记的是在一本关于产生式系统的书里看来的),人的脑袋瓜子里面同一时刻只能暂存5~9个临时记忆块。而变量就是你在看代码的时候不得不在它的整个生命周期中都必须费劲跟踪的东西。所以同一时刻要关注的变量越多,你的脑子就越忙不过来,就不得不经常在代码里头跳来跳去。
而在C99支持混合声明以后,你就可以游刃有余地在编码时淋漓尽致地运用变量引用局部化原则(详见神书CC2e)化解这些可读性问题。不然如果非得把变量都堆前头声明的话,什么缩短变量的跨度、减小变量攻击窗口、最小化变量的生存时间之类的技巧都很难充分实施。而另外CC2e中提到的利用中间变量来提高代码可读性,实现代码自注释的技巧在C99里面实施起来也会容易很多——因为变量定义起来方便多了,而且即使定义了太多变量也不会把每个Block开头的变量列表给弄得太夸张。所以从这个角度来看,这也是一个强烈推荐使用,应该写入编码规范的特性。
// 行注释
//行注释也是从C++过来的东西。实际上在C99出来之前用//写注释和随处定义变量基本上是C菜鸟的标准特征了……至少会被人鄙视为“不专业”。所以老手们要开始在代码里面接受这两个东西弄不好反倒会有些心理障碍~~比如我至今还不太习惯在C代码里面敲//~~
不过//做行注释也有不少好处。一个自然是输入方便——但是也和你用的工具有关了~~如果你的代码编辑器足够高级(如SlickEdit),用/* */写注释也不会有啥不爽的~~SE的注释自动补全和格式化能力强到变态。即使是你用/* */写了一大堆洋洋洒洒的、左边带有漂亮的前导“ * ”边界的文档化注释……也只有开头的“/*”这两个字符是要你自个敲的~~不过用SourceInsight这种弱智玩意的人就比较悲催了,想“//”想得最凶的一定是他们(# ̄▽ ̄#)。
//注释另外一个最大的好处还是在于排版方便:在为常量或变量写Trailing Comments的时候,用//写注释你只要维护左侧的对齐就能有很好的观感。而用/* */,则不得不首位兼顾。作为一个完美主义者,小心地维护一大堆/* */首尾的精美对齐是一件挺烦人的事情。比如这样代码,我就是忍受不了的:
#define PID_OFF 0x10 /* TCB->pid */
#define USER_CONTEXT_OFF 0x14 /* TCB->user_context */
#define MM_OFF 0x50 /* TCB->mm */
#define UPFLAGS_OFF 0x54 /* TCB->usr_proc_flags */
#define SIGNAL_OFF 0x58 /* TCB->signal */
#define EX_FIXUP_OFF 0x5C /* TCB->ex_fixup */
非得把它搞成这样才爽:
#define PID_OFF 0x10 /* TCB->pid */
#define USER_CONTEXT_OFF 0x14 /* TCB->user_context */
#define MM_OFF 0x50 /* TCB->mm */
#define UPFLAGS_OFF 0x54 /* TCB->usr_proc_flags */
#define SIGNAL_OFF 0x58 /* TCB->signal */
#define EX_FIXUP_OFF 0x5C /* TCB->ex_fixup */
即使有SlickEdit强大的Block功能的帮助,维护这么个两头对齐的排版还是很麻烦的。最烦的就是要是哪天又加了点注释结果长度捅出去了,有排版洁癖的我又会忍不住把所有注释的*/都往后调整以便对齐。。。而用了//以后,只管一边自然容易许多,而且看上去也还整齐:
#define PID_OFF 0x10 // TCB->pid
#define USER_CONTEXT_OFF 0x14 // TCB->user_context
#define MM_OFF 0x50 // TCB->mm
#define UPFLAGS_OFF 0x54 // TCB->usr_proc_flags
#define SIGNAL_OFF 0x58 // TCB->signal
#define EX_FIXUP_OFF 0x5C // TCB->ex_fixup
所以对于这类Trailing Comments来说,用//写比较合适。
//还有一个好处就是没有/* */的嵌套问题。/* */注释是不支持嵌套的,所以只要里头出现一个*/就会导致注释结束。不过这样也带来了一个有点可怕的陷阱,比如原来用/* */注释的宏:
#define macro(arg1, arg2) \
func(arg1, /* xxxxxxx */ \
arg2 /* xxxxxxx */ \
)
你要是改成这样:
#define macro(arg1, arg2) \
func(arg1, // xxxxxxx \
arg2 // xxxxxxx \
)
这就杯具了~~因为这样等价于:
#define macro(arg1, arg2) func(arg1, // xxxxxxx arg2 // xxxxxxx )
arg2就落入第一个//的魔掌,变成注释了~~
总得来说,用//写注释肯定是比较方便的~~只要能保证别在一个文件里面一会儿用/* */做行注释,一会儿用//做行注释就行了。至于具体的用法,个人建议所有的单行注释都可以用//搞。而文档化注释(函数头部、文件头部或者重要数据结构头部的那些注释),还是建议用正统的格式来写:
/**
* xxxxxxx
*/
另外最好别用//来垒多行注释。
for循环变量初始化(for loop intializers)
一个只在for循环内部使用的变量,却非得在循环体外面定义,并且在循环体外也可见——这个事情太没道理。于是C99终于也忍不住引入了C++中广为人知的for循环变量初始化方式:
for (int i = 0; i < n; i++) {
...;
}
除了写起来方便以外,循环变量的生存周期也被最小化了。这也顺便杜绝了那种把循环变量带到循环外头继续用的恶习(有人称之为“技巧”)。C99是鼓励在for循环中使用这种写法的,所以赶快把这条也写到你的编码规范里去吧。
变参宏(Variadic Macros)
变参宏的最大好处是可以让你很容易地用宏来封装一些带变参的函数(主要是打印函数),如可以这样写一个输出到stderr的宏:
#define print_err(...) fprintf(stderr, __VA_ARGS__)
宏参数里面“...”的部份会展开到__VA_ARGS__处。如果在__VA_ARGS__前面加上##,就可以写出允许变参部份为空的变参宏。比如我自己常用的调试信息打印宏:
#define debug(fmt, ...) \
printf("[DEBUG] %s:%d <%s>: " fmt, \
__FILE__, __LINE__, __func__, ##__VA_ARGS__)
##__VA_ARGS__表示变参“...”部份允许为空。当变参部份为空时__VA_ARGS__会展开成空字符串,并且##前面那个逗号也会在展开时去掉。于是你可以这样调用这个宏:debug("Hello");
__func__常量
提到了我的debug()宏就顺便说一下:__func__也是C99标准了~~功能是会被展开为当前函数的名字,用法见上。
【看来还是未完,待下回分解~~】