• gcc对void型指针的类型检查有问题?


    背景

    前一段时间遇到一个问题,我调用了一个隔壁部门的库,这个库在调用前要创建context,程序退出前要销毁context,问题是销毁的时候,出现段错误了。
    经过一番定位,发现是库的销毁接口跟库的常规处理接口稍有差别

    typedef void *xxx_handle;
    //常规接口
    int xxx_proc(xxx_handle h, int arg);
    //创建接口
    xxx_handle xxx_create();
    //销毁接口
    int xxx_destroy(xxx_handle *ph);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    销毁接口的第一个入参类型不是void*,而是void **
    等等,为什么编译没有告警指针类型不匹配?

    gcc对void *的检查

    下面这行代码不会有任何告警,但指针类型其实是不匹配的:

    #include
    
    typedef void *handle;
    
    void f(handle *ph) {
    }
    
    int main() {
        handle h = NULL;
        f(h);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    这说明,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) {
          ^
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    看样子是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) {
          ^
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    那能否统一用int*做handle来规避上述问题呢?恐怕不太行,因为函数接口将形参定义成void *就是为了方便调用者传参时不用进行强制类型转换,改成int*后,就必须强转了。

    怎么解决?

    我试了以下方法:

    1. voidtypedef成VOID以创造新的基类型,不好使
    2. handle *typedef成phandle以显式区别2种void指针类型,也不行

    看样子只能程序员自己注意了?好悲催啊!
    等等,有没有可能,编译器就是不想区分void*void **呢?毕竟指针本身就是可以值传递的,为什么还要脱裤子放屁地搞个二级指针呢?

    更改编程习惯

    回头看我们的库接口设计,是有问题的,create返回context的地址给调用者,那么调用者在destroy执行完毕后当然有义务将记录的context置空,但是库作者为了“易用性”,选择在destroy里自己置空,而这势必要获取调用者记录context地址的指针的地址,这就“越界”了!
    正确的做法是destroy只销毁context,至于调用者可能忘记置空context指针导致的内存访问问题,由调用者自己操心

    总结

    1. C语言中尽量不要使用void **参数
    2. 用户态逻辑尽量用C++来写,不能改的话,至少用C++编译器来编译吧。
  • 相关阅读:
    Hive-SQL
    pytorch_神经网络构建2(数学原理)
    蓝桥杯---第二讲---二分与前缀和
    JAVA毕业设计航空订票系统计算机源码+lw文档+系统+调试部署+数据库
    学python的第十八天
    【WSN】无线传感器网络 X-Y 坐标到图形视图和位字符串前缀嵌入方法研究(Matlab代码实现)
    Jmeter基础入门教程【12】--常用功能详解:JDBC
    LabVIEW定义自定义错误代码在应用程序中使用
    JUC并发工具-CAS机制
    如何从Linux 的用户空间检查设备树信息?
  • 原文地址:https://blog.csdn.net/happen23/article/details/127785490