free一个合法的地址也会导致crash?

场景描述

在Windows平台上使用C++开发了一个服务,其中组合了各种各样的第三方组件,一般以lib/dll和头文件的形式使用。有这样一种场景,如下图所示,应用程序申请了一段内存ptr, 但在调用lib.dll的函数接口中其调用了free(ptr)。一般来说我们也尽量避免在一个组件中申请内存,而在另一个组件中释放,这里恰巧是一个bug导致了跨组件的内存申请和释放。
在这里插入图片描述
那么请各位读者思考一下,这样会有问题吗?如果你是一个老司机,也许已经发现,在某些情况下会在调用free(ptr)的时候导致程序crash。

为何crash

熟悉Windows编程的读者应该了解如下图所示,操作内存的方式有如下几种:
(1) 直接VirtualAlloc之类的函数,可以申请一段虚拟地址空间,并且使用这段空间
(2) 直接使用HeapCreate, HeapAlloc之类的函数,来创建堆,并且使用堆。也就是说Windows的进程可以有多个堆,一般进程启动有一个默认的堆。而Heap的底层实际是采用VirtualXXX之类的函数进行控制的。
(3) Local或者Global Memory API,主要是从进程默认堆中申请或者释放内存
(4) CRT库中调用malloc去申请内存,而这里是本文的重点。其底层实际是使用的Windows Heap API实现。
(5) 最后一个就是Memory Mapped File。
在这里插入图片描述
在第一个章节我们描述的内存申请释放场景,是采用了CRT库的方式。CRT库的链接方式有四种:

  • /MT 静态链接进你的组件。也就是说当你采用这个编译选项的时候CRT的的代码也被链接进了你的DLL或者Exe。
  • /MD 这种链接方式,实际上在应用程序运行的时候,才会加载对应的CRT库的DLL。
  • /MTd/MDd主要针对Debug Build,链接的方式和上面两种一一对应,不再赘述。
    在这里插入图片描述
    那么我们再来看下第一章节的场景,他们分别采用如下编译方式:
  • APP.exe采用/MD编译,也就是会在运行的时候,装载CRT库的DLL,调用的malloc也是在这个DLL里面。
  • lib.dll 采用的/MT编译,那么在调用free的时候会调用链接在lib.dll中的CRT库中的free

留给读者一点时间,这样的场景调用会有什么问题呢?

这个问题涉及到的是CRT库malloc的实现问题,之前我们已经了解到了CRT库采用的是HeapXXX 相关的API。那么CRT库是采用HeapCreate创建了新的堆,还是使用进程默认堆呢?

微软的CRT库是开源的,lib.dll采用的是VS2010编译的,CRT库会使用HeapCreate创建新的堆。而APP.exe采用的是VS2015编译的 (因为App.exe和lib.dll不是同一个团队做的,有可能编译器版本不同),其对应版本的CRT库是使用的系统默认堆。那么APP.exe中malloc的内存是系统默认堆里申请并且管理的,而在lib.dll中free却会从自己创建的堆中去寻找,寻找不到对应的分配的地址,从而导致了程序Crash。

那么这个章节留两个问题给大家,如果APP.exe和lib.dll继续使用原先的链接CRT库的方式:

  1. APP.exe和lib.dll均采用VS2010编译,第一章节的场景还会Crash吗?
  2. APP.exe和lib.dll均采用VS2015编译,第一章节的场景还会Crash吗?

如果这两个问题能够回答正确,说明你已经理解这个问题啦。如果没有答出来,欢迎我们一起讨论。

建议

在编程的道路上,到处都是坑,有新的挖的也有前人留下的坑。那么就尽量用自己的经验去防御性编程,减少可能存在的坑:

  1. 在一个应用程序中,所有自己可控的组件均采用/MD的方式去链接CRT库
  2. 尽量不要在一个模块中申请内存,在另外一个模块中释放。比如你实现一个动态链接库(DLL),提供一个接口FuncA申请并返回内存地址,那么最好提供一个接口FreeXX去释放FuncA申请的内存。

最后是个人微信公众号,文章CSDN和微信公众号都会发,欢迎一起讨论。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/CJF_iceKing/article/details/114455857
今日推荐