浅析void*

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
    • 一、我是怎么写错的
    • 二、错上加错
    • 三、根因分析
  • 总结
        • void*的释放
        • void*不可以解引用
        • 代替泛型的轻量化实现


前言

好久没写了,记录一下前段时间用void*的错误用法


一、我是怎么写错的

我就不上来说void*是啥啥啥,然后再来一堆指针的预备知识了。

handle_t handle = reinterpret_cast<handle_t>(...func());

class A {
 public:
  void** func() {
    return reinterpret_cast<void**>(handle_);
  }
 private:
  handle_t handle_;
};

以上是正确的写法,一个典型的使用void*进行透传的场景。然后看一下我的sao操作:

handle_t handle = reinterpret_cast<handle_t>(...func())

class A {
 public:
  void** func() {
    return handle_;
  }
 private:
  void** handle_;
};

几乎没变,只是把handle_初始数据类型从handle_t变成了void**, 少了一次强转。然后就出问题了,传上去发现拿到的handle是空的,这里少了一个A类中handle_去CreateHandle()的操作,大家自行脑补一下我就不写了。

二、错上加错

发现上面写错之后,我分析了一波,觉得是void**这个二级指针的锅,我认为void** -> handle_t->void**(这里void**->handle_t就是上面说的要脑补的细节,我稍微展开一下:CreateHandle得传入一个&handle_,这里肯定会转成handle_t去处理,CreateHandle不可能接受一个void**的入参) 。
因为void**是二级指针,所以它指向的东西也只分配了一个指针那么大的空间,也就是64位(一个地址的长度)所以当handle_t指向的资源实际内存占用大于64位的时候就会出错。然后我就大聪明了一波把func()的返回值从void**改成了void*, 用一级指针去对一级指针没毛病,说干就干,动手能力还挺强。改完发现涛声依旧啊,老老实实用回了原来的写法,果然好使(费话)

三、根因分析

改回最开始的写法之后,我就逐渐把这个问题忘了。。了。。这也是这篇博客出现间隔这么长的原因之一。。
正经分析一下:void*这类指针呢(忽略void**甚至void***这些多级指针,本质上是一样的,只是一个媒介)很灵活,可以指向任意类型的数据,也即任何类型的指针都可以直接赋值给它。用起来可能会有一种操作内存的感觉? 反正void*起到了帮我们和编译器屏蔽冗余的变量信息的作用,也就连带出了它一个根本的属性,就是它只能作为中间人的身份出现(即我可以有int* -> void* -> int* char* -> void*-> int*之类的操作,忽略这些操作的安全性问题),那我之前那种写法就注定了失败,你总得先把一些信息告诉编译器,然后开始秀,没有一开始就啥信息没有(void*)就开始一顿操作的这种做法,编译器它做不到啊太难了。。

总结

来点官方的总结:


void*的释放

错误的做法:
直接delete。对于系统内置类型是没有问题的;如果void*指向一个数组指针,这么做就会发生内存泄露,可以delete[];当然这种方式都会有编译警告:

warning: deleting ‘void*’ is undefined [enabled by default]

因为编译器认为这是一种不安全的做法(也确实是)void*不向int*之类编译器可以明明白白知道它指向的内存空间有多大,比如int,但是void*就不行。如果指向一个类,那delete就会出错,因为编译器不知道这个指针指向一个什么类型的变量,自然也不会主动去调这个类的析构函数。
正确的做法:
将void*转换成原数据类型,然后再释放。

void*不可以解引用

int num = 10;
void* ptr = &num;
*ptr++;	

代替泛型的轻量化实现

加快编译速度(因为编译时的东西转化成运行时去做了。。肯定加快)但是也容易引入一些隐藏的bug(没了编译检查)所以用的时候要稍微注意一下。

你可能感兴趣的:(C++)