C语言的语法极其简洁,即使是初次接触编程语言的初学者也能很快学完它的语法。不过,C语言也是一门“灵活得过了头”的编程语言,对于很多初学者来说,编写C语言程序就好像拿着一堆最基本的砖块,要修建一座大厦一样,茫然找不到方向。
奇怪的C语言代码
对于初学者来说,阅读项目源代码是学习和巩固C语言编程能力的一个好方法——从前辈们的一些优秀C语言项目中,我们能够学到很多编写程序方面的思考方式,也就是一些程序员所谓的“编程思维”,看得多了,编写C语言程序自然就手到擒来了。
不过虽然C语言的语法简单,但是我们总会看到一些令人迷惑的代码,例如下面这个函数,它来自某个开源项目,为了讨论主题,我将一些不相关的细节略去了:
void *fun(void *ud, void *ptr, int size){
(void) ud;
(void) size;
// 其他代码,未用到 ud 和 size 参数
...
}
fun() 函数中省略掉的代码没有使用到 ud 和 size 参数,这里有两个问题:一是既然用不到这两个参数,为什么不删去它们呢?再就是两个参数前的 (void) 类型转换有什么用呢?
首先考虑第一个问题
前文提到 fun() 函数来自一个C语言程序开源项目,该项目比较复杂,但是我们知道再复杂的程序项目也是一行一行代码敲出来的,而且,在后续的开发中,可能会修改之前的设计。明白这一点,要回答第一个问题就简单了。
可能在之前的设计中,fun() 函数是用到了 ud 和 size 参数的,只是后来的设计发现 fun() 函数不必使用这两个参数,但是发现整个C语言项目由大量使用 fun() 函数的代码。
如果删去这两个参数,那么 fun() 函数的原型就改变了,开发人员将不得不逐个修改整个C语言项目中所有调用 fun() 函数的代码,这样的工作量巨大,极其容易给C语言项目引入 bug。因此,倒不如继续保留 fun() 函数的原型不变了。
另外,读者应该已经知道C语言是不支持重载的,因为如果该C语言项目需要使用 fun() 函数对接某些 API,那么fun() 函数就必须符合 API 指定的原型,因此 fun() 函数中有未使用的参数其实是“身不由己”的。
还有一种情况,fun() 函数可能是某个“函数家族”里的一个,该“函数家族”由一个统一的函数指针管理(为了方便,以及提高效率,实例可参考我之前文章。),因为“统一的函数指针”类型是固定的,所以 fun() 函数的原型必须符合该函数指针的原型,所以,即使 fun() 函数用不到 ud 和 size 参数,也是不能将其删除的,否则就无法通过“统一的函数指针”调用 fun() 函数了。
当然了,也有可能纯粹是因为开发人员懒得修改 fun() 函数原型。现在明白了第一个问题,再来考虑第二个问题。
为什么要在未使用的参数前添加 (void) 呢?
在解答这个问题之前,我们先做一个实验:编写下面这段C语言代码,也即删去 (void)ud 和 (void)size:
void *fun(void *ud, void *ptr, int size){
// 其他代码,未用到 ud 和 size 参数
...
}
在编译这段C语言代码时,编译器常常会给出下面这样的“参数未使用(unused parameter)”警告信息:
t.c: In function ‘fun’:
t.c:3:22: warning: unused parameter ‘ud’ [-Wunused-parameter]
很多C语言程序员会忽略编译器发出的警告信息,但这是非常不好的习惯,解决警告信息能够帮助我们最大程度的避免最终C语言程序出现bug。要解决“参数未使用(unused parameter)”警告信息,最直接的方法就是使用它了:
void *fun(void *ud, void *ptr, int size){
ud;
// 其他代码,未用到 ud 和 size 参数
...
}
但是编译器又会给出“C语言语句无效”的警告信息:
t.c:5:5: warning: statement with no effect [-Wunused-value]
ud;
^~
为了避免出现这样的警告信息,我们当然可以对 size 和 ud 参数做一些其他操作,例如:
void *fun(void *ud, void *ptr, int size){
ud = (void *)size;
// 其他代码,未用到 ud 和 size 参数
...
}
可是这样虽然能够避免C语言编译器发出警告,但是这样会让其他阅读代码的程序员费解:“NND,ud = (void *)size;
这句到底什么意思呢?”
因此,避免编译器发出参数未使用的警告信息,最好不要像上面这样操作,采用 (void) 操作更好:
void *fun(void *ud, void *ptr, int size){
(void) ud;
(void) size;
// 其他代码,未用到 ud 和 size 参数
...
}
C语言程序员都知道 void 表示空,因此(void)ud 和(void)size 显然表示不关心 ud 和 size 的操作。这样一来,我们的意图一眼就能看出,而且还能避免编译器发出警告信息。
小结
在C语言程序开发中,定义函数时,有时会不可避免的定义一些使用不到的参数,这时编译程序时,编译器往往会发出警告信息。C语言程序员不应该忽视每一个警告信息,因此可以借助 (void)操作屏蔽掉这样的警告信息,以免更重要的编译器警告被淹没在信息流里。
点赞