std::vector的错误使用

上周五服务器线上出现了几次crash,拿回dump文件分析后发现代码是崩在了对一个引用的成员变量赋值上。

分析了半天也没看出来代码有什么不妥,就先搁置了。

今天同事又给我看了一段奇怪的代码,某个类成员函数返回了某个成员变量的引用,但是当指针为NULL时去调这个函数依然不会崩溃。

思来想去搞不明白,反汇编之后终于发现原因所在。

c++引用本质上也是指针,只是不能为NULL而已。因此返回引用其实就是返回这个变量的内存地址。也就是说这个函数实际的操作仅仅是拿this指针加上这个成员变量的偏移量,然后将结果返回给引用变量。这个操作从始至终都没有没有去操作内存,当然也不会崩溃。

解决了这个疑问之后,又想起来上周五的崩溃。再次分析了一下代码,想看看是否是因为相同的问题引起的。

花了两个小时之后终于发现,其实是误用vector引起的。

这段代码的作者使用vector实现了一个结构体池,每次申请结构体时从池中获取,释放时归还到池中。

结构体池的定义类似std::vector<struct xxx> pool;

之所以产生bug是因为,每次当vector中的元素被使用完之后,都会掉用resize来将vector的容量加倍。

熟悉vector的人都知道,vector本质上就是一个数组,当大小不够时就重新分配一块更大的内存并释放掉原先的旧内存。这会导致vector中元素的内存地址全部改变。

在调用池的分配函数时,已经把相应元素的内存地址返回给了逻辑代码。vector内存地址的改变势必会导致在操作以前分配出去的元素时会出现访问错误内存。

bug正是这样产生的,函数a从池中申请了一个元素,然后掉用了函数b。函数b又从池中分配了一个元素,恰好池中元素用完了,导致了vector进行resize。当返回到函数a时,在对以前返回的元素进行操作实际上是非法的。因为这块内存已经被释放掉了,而相关数据也已经被挪到新内存了。

关于用DLL接中使用std::vector之后出现的问题

最近在代码中用了这样一个DLL,采用静态加载方式使用,原型类似如下:

XXX_API  int xx_func(std::vector<struct xx> &xx_tbl, ..., ...);
//代码中会用xx_tbl.push_back(xx);之类的代码向xx_tbl里面填充数据

但是却出现一个奇葩问题,每当调用这个DLL的程序退出时Debug版本有很大概率会崩溃在这个std::vector<struct xx>的析构函数上。

研究了好久才发现,当DLL中调用push_back函数时,其实std::vector<struct xx>的构造函数分配的内存是属于这个DLL的资源,当程序退出时会首先卸载这个DLL程序,那么与他相关的内存也随之被释放。

当主程序最后退出时,就会引发xx_tbl的析构函数,但是由于xx_tbl中的某些元素的内存是在DLL中分配的,而且已经被释放了,那么这些内存在被析构函数释放时就会引出错误,Debug版的代码是有内存检查的,因此每次Debug代码退出时就会崩溃。

因此,对于DLL中尽量采用纯C的结构,不要使用对象。

————————————————————————————————–
后来找到原因,因为动态链接库使用的CRT库是静态链接的,与Exe程序使用的是两个不同的CRT,因此在DLL中向vector中push数据之后会,会调用DLL链接的CRT进行分配内存,如果在Exe中使用了被DLL操作过的vector变量时,就会导致vector变量进行resize,而由于Exe与DLL使用的是两个CRT库,这时就会造成信息错乱,引起程序崩溃,只要将所有DLL设成使用动态加载CRT即可解决此问题。