前一段时间遇到一个问题,我调用了一个隔壁部门的库,这个库在调用前要创建context,程序退出前要销毁context,问题是销毁的时候,出现段错误了。
经过一番定位,发现是库的销毁接口跟库的常规处理接口稍有差别
typedef void *xxx_handle;
//常规接口
int xxx_proc(xxx_handle h, int arg);
//创建接口
xxx_handle xxx_create();
//销毁接口
int xxx_destroy(xxx_handle *ph);
销毁接口的第一个入参类型不是void*
,而是void **
等等,为什么编译没有告警指针类型不匹配?
下面这行代码不会有任何告警,但指针类型其实是不匹配的:
#include
typedef void *handle;
void f(handle *ph) {
}
int main() {
handle h = NULL;
f(h);
return 0;
}
这说明,gcc不会告警一级void指针和二级void指针的类型不匹配!
尝试用g++
编译问题代码,直接报error:
test_void_ptr.c: In function ‘int main()’:
test_void_ptr.c:10:8: error: invalid conversion from ‘handle {aka void*}’ to ‘void**’ [-fpermissive]
f(h);
^
test_void_ptr.c:5:6: note: initializing argument 1 of ‘void f(void**)’
void f(handle *ph) {
^
看样子是C语言编译器特有的现象,C还是太permissive了。
但是你如果把void
关键字替换成int
,其他不做修改,则gcc立马报incompatible pointer type
告警:
test_void_ptr.c: In function ‘main’:
test_void_ptr.c:10:7: warning: passing argument 1 of ‘f’ from incompatible pointer type [-Wincompatible-pointer-types]
f(h);
^
test_void_ptr.c:5:6: note: expected ‘int **’ but argument is of type ‘handle {aka int *}’
void f(handle *ph) {
^
那能否统一用int*
做handle来规避上述问题呢?恐怕不太行,因为函数接口将形参定义成void *
就是为了方便调用者传参时不用进行强制类型转换
,改成int*
后,就必须强转了。
我试了以下方法:
void
typedef成VOID
以创造新的基类型,不好使handle *
typedef成phandle
以显式区别2种void
指针类型,也不行看样子只能程序员自己注意了?好悲催啊!
等等,有没有可能,编译器就是不想区分void*
和void **
呢?毕竟指针本身就是可以值传递的,为什么还要脱裤子放屁地搞个二级指针呢?
回头看我们的库接口设计,是有问题的,create返回context的地址给调用者,那么调用者在destroy执行完毕后当然有义务将记录的context置空,但是库作者为了“易用性”,选择在destroy里自己置空,而这势必要获取调用者记录context地址的指针的地址,这就“越界”了!
正确的做法是destroy只销毁context,至于调用者可能忘记置空context指针导致的内存访问问题,由调用者自己操心
void **
参数C++
来写,不能改的话,至少用C++
编译器来编译吧。