之前在做内存泄漏分析模块功能开发时,发现在windows下的输出结果并不是很准确,很多内存泄漏都显示是在windows的api或crt函数中,比如CoInitializeEx,stderror,IsOS等。

以CoInitializeEx为例,调用CoUninitialize后,它申请的内存并没有相应的free掉;stderr中,申请的内存也没释放;

下面分别对这些API深入分析下,看看它们的内存分配和释放到底是如何进行的;

stderror

内存分析模块捕捉到的堆栈信息为:

4 allocate: addr = 0000017BD340F610, size = 18600007FF67B08F81B  _calloc_dbg() at f:\dd\vctools\crt\crtw32\misc\dbgheap.c:652(0x7b08f7d0)00007FF67B087A56  strerror() at f:\dd\vctools\crt\crtw32\misc\strerror.c:75(0x7b087a00)00007FF67A718E8B  luaL_fileresult() at e:\lib_aux.c:39(0x7a718de0)00007FF67A8A3D54  lj_cf_io_open() at e:\lib_io.c:420(0x7a8a3c90)

第3个栈帧lib_aux.c:39调用的就是stderror函数;
     我们直接看stderr的源码(CRT VC12):

wchar_t * cdecl _wcserror(
#else  /* _UNICODE */
char * __cdecl strerror (
#endif  /* _UNICODE */int errnum)
{_TCHAR *errmsg;_ptiddata ptd = _getptd_noexit();if (!ptd)return _T("Visual C++ CRT: Not enough memory to complete call to strerror.");if ( (ptd->_terrmsg == NULL) && ((ptd->_terrmsg =_calloc_crt(_ERRMSGLEN_, sizeof(_TCHAR)))== NULL) )return _T("Visual C++ CRT: Not enough memory to complete call to strerror.");elseerrmsg = ptd->_terrmsg;#ifdef _UNICODE_ERRCHECK(mbstowcs_s(NULL, errmsg, _ERRMSGLEN_, _get_sys_err_msg(errnum), _ERRMSGLEN_ - 1));
#else  /* _UNICODE */_ERRCHECK(strcpy_s(errmsg, _ERRMSGLEN_, _get_sys_err_msg(errnum)));
#endif  /* _UNICODE */return(errmsg);
}

_getptd_noexit()函数实现:

_ptiddata __cdecl _getptd_noexit (void)
{_ptiddata ptd;DWORD   TL_LastError;TL_LastError = GetLastError();if ( (ptd = __crtFlsGetValue(__flsindex)) == NULL ) {/** no per-thread data structure for this thread. try to create* one.*/
#ifdef _DEBUGextern void * __cdecl _calloc_dbg_impl(size_t, size_t, int, const char *, int, int *);if ((ptd = _calloc_dbg_impl(1, sizeof(struct _tiddata), _CRT_BLOCK, __FILE__, __LINE__, NULL)) != NULL) {#else  /* _DEBUG */if ((ptd = _calloc_crt(1, sizeof(struct _tiddata))) != NULL) {#endif  /* _DEBUG */if (__crtFlsSetValue(__flsindex, (LPVOID)ptd) ) {/** Initialize of per-thread data*/_initptd(ptd,NULL);ptd->_tid = GetCurrentThreadId();ptd->_thandle = (uintptr_t)(-1);}else {/** Return NULL to indicate failure*/_free_crt(ptd);ptd = NULL;}}}SetLastError(TL_LastError);return(ptd);
}

它首先通过TLS查找线程相关数据,如果没有找到,就分配一块内存,存放_tiddata结构,并将这块内存与__flsindex相关联。

TLS是Win32中常用的存取线程相关数据的一种技术,由操作系统的Tls*系列函数提供支持。

例如,可以在程序开始的地方调用TlsAlloc()函数,获得一个TLS index,这个index在进程范围内有效,然后可以创建n个线程,在每个线程中使用TlsSetValue(index,data)将线程相关数据和index关联起来,使用TlsGetValue(index)来获取当前线程和index相关联的的线程相关数据。

(顺便提一句,经常说的要使用_beginthread而不是CreateThread,也是因为当线程函数调用errno或localtime或其他需要TLS支持的函数时,这些函数会调用_getptd_noexit()函数初始化一个VC运行时库的TLS数据,当线程函数退出时,这块内存不会自动释放,因此产生了泄漏)

所以,stderror主要有两处地方申请内存(虽然有包含关系,但还是分开说):
     1,_ptiddata 线程相关数据
     2,ptd→_terrmsg

从上面的分析也不难推断出:ptd→_terrmsg的释放是在当前线程退出时,释放_ptiddata时一起进行的。
     代码为:

_CRTIMP void
WINAPI
_freefls (void *data){_ptiddata ptd;pthreadlocinfo ptloci;pthreadmbcinfo ptmbci;/** Free up the _tiddata structure & its malloc-ed buffers.*/ptd = data;if (ptd != NULL) {if(ptd->_errmsg)_free_crt((void *)ptd->_errmsg);if(ptd->_namebuf0)_free_crt((void *)ptd->_namebuf0);if(ptd->_namebuf1)_free_crt((void *)ptd->_namebuf1);if(ptd->_asctimebuf)_free_crt((void *)ptd->_asctimebuf);if(ptd->_wasctimebuf)_free_crt((void *)ptd->_wasctimebuf);if(ptd->_gmtimebuf)_free_crt((void *)ptd->_gmtimebuf);if(ptd->_cvtbuf)_free_crt((void *)ptd->_cvtbuf);if (ptd->_pxcptacttab != _XcptActTab)_free_crt((void *)ptd->_pxcptacttab);_mlock(_MB_CP_LOCK);__try {if ( ((ptmbci = ptd->ptmbcinfo) != NULL) &&(InterlockedDecrement(&(ptmbci->refcount)) == 0) &&(ptmbci != &__initialmbcinfo) )_free_crt(ptmbci);}__finally {_munlock(_MB_CP_LOCK);}_mlock(_SETLOCALE_LOCK);__try {if ( (ptloci = ptd->ptlocinfo) != NULL ){__removelocaleref(ptloci);if ( (ptloci != __ptlocinfo) &&(ptloci != &__initiallocinfo) &&(ptloci->refcount == 0) )__freetlocinfo(ptloci);}}__finally {_munlock(_SETLOCALE_LOCK);}_free_crt((void *)ptd);}return;
}

调用流程为:

_endThread → _freeptd ->_freefls 。

因此,stderor申请的内存,只有在当前线程结束时,才会释放。(errno也一样)

CoInitializeEx

CoInitializeEx接口就没有源码分析了,只能通过microsoft的文档来理解:


     CoInitializeEx接口是用来初始化COM库的,且每个线程至少一次调用(允许多次);

既然是初始化,且线程间相互独立,可以大胆推测内部也有线程相关的数据空间的分配,它的释放也要等到线程结束或进程结束。

CoInitializeSecurity

内存分析模块捕捉到的堆栈信息为:

80 allocate: addr = 000001A722464900, size = 25600007FFAEE9DCF76  error: 487, bRet = 100007FFAEF8F1AD0  CFastBH::CreateFromBindingString() at onecore\com\combase\common\core\fastbh.cxx:176(0xef8f1a68)00007FFAEF8B1370  <lambda_24b83035e99092592b8833e72f33f0cc>::operator()() at onecore\com\combase\dcomrem\resolver.cxx:609(0xef8b10d0)00007FFAEF882559  CRpcResolver::GetConnection() at onecore\com\combase\dcomrem\resolver.cxx:875(0xef8824cc)00007FFAEF8B2465  CoInitializeSecurity() at onecore\com\combase\dcomrem\security.cxx:3216(0xef8b23c0)00007FF716EE8D14  get_bios_uuid_from_wmi() at e:\src\lib\dmidecode\windows\smbios.cpp:607(0x16ee8bb0)

在smbios.cpp:607处调用了CoInitializeSecurity接口;倒数第二帧又调用了CFastBH::CreateFromBindingString()接口,找不到任何的接口信息,只能根据名称推测,内部创建了String相关的结构。但是没有找到释放的逻辑。

结论:

从上述分析看,wind32 api或crt内部申请的内存,不一定有相对应的接口直接进行释放,而是会在合适的时机才释放:
1,部分线程相关结构/数据,在线程结束时,进行了清理;–比如stderror,CoInitializeEx
2,部分进程相关结构/数据,在进程结束时,进行了清理;–CoInitializeEx的部分内存可能在进程退出时释放。
3,部分全局结构/变量,在进程结束时,进行了清理; --比如_localtime64_s

win32 api内部实现资料实在太少,没有代码,上述结论都是基于microsoft doc以及调试得出的,不一定100%准确。

Win32 API中内存的申请与释放相关推荐

  1. c语言清理内存程序,C语言中 内存的申请与释放

    内存的申请与释放 对于一段内存的数存,该如何解释,是依赖于数据类型,需要使用 malloc,其使用语法如下: void * malloc(size_t size); 函数 malloc 包含在头文件为 ...

  2. 动态内存的申请和释放

    动态内存的申请和释放 文章目录 动态内存的申请和释放 1. malloc() 和 free() 的基本概念以及基本用法 1.1 函数原型及说明: 1.2 被释放的指针 1.3 注意事项 2. mall ...

  3. C/C++代码调试:快速定位内存的申请和释放的位置

    1.问题 如果大型项目中出现类似于*** glibc detected *** logcacheinit: double free or corruption (fasttop): 0x0000000 ...

  4. 串口编程之一: WIN32 API 中串口DCB 结构的介绍

    在应用WIN32  API 对串口进行编程时, 必定会使用到DCB 结构. 下面的DCB 结构的一些介绍. 首先是DCB 结构. typedef struct _DCB {           DWO ...

  5. win32 api中的Beep,PlaySound,MessageBeep等一系列函数编译通过但是没有声音的原因

    1.win32 api是基于x86架构的应用接口,也就是32位的,如果使用了x64的架构这几个函数就无法发出声音. 2.如果出现了链接错误一般是你的项目中没有包含相关的库文件,Beep,Message ...

  6. 动态链表的创建、节点内存空间申请以及释放

    1.动态链表的初始化: typedef struct _STACK{ void* data;     int size;     struct _STACK* next;     struct _ST ...

  7. Linux——内存的申请与释放

    这篇博客,我们主要解决以下的两个问题: malloc()申请1G的内存能否成功?判断依据是什么? 申请了一块空间没有free,进程结束,那么空间是否被回收? 如下程序完成编译后,一边运行一边查看资源管 ...

  8. lpvoid 在 win32 api 中的使用

    lpvoid在msdn中的描述为: LPVOID Generic pointer type, equivalent to (void *). Should be used instead of LPS ...

  9. 内存申请与释放(转)

    释放内存?那要看你怎么申请的了 new->delete;malloc->free;GlobalAlloc->GlobalFree;VirtualAlloc(Ex)->Virtu ...

最新文章

  1. 机器学习泰斗迈克尔 · 乔丹:不是什么都叫AI的
  2. 基于VMwareWorkstation技术预览版2012上的WinServer8测试版安装
  3. linux centos lamp,Centos下搭建LAMP
  4. 【译】Economics of Fees and Gas
  5. TCP窗口大小的利与弊转载自
  6. Nginx的rewrite之if指令(二)
  7. JavaScript使用技巧精萃 经典代码收藏版
  8. 坑爹!千万不要在生产环境使用控制台日志
  9. java 队列_百战程序员:Java并发阻塞队列
  10. linux 查看握手时间,实战:tcpdump抓包分析三次握手四次挥手
  11. 第六届省赛(软件类)真题----Java大学A组答案及解析
  12. 笔记︱风控分类模型种类(决策、排序)比较与模型评估体系(ROC/gini/KS/lift)
  13. 2019最新版QQ音乐api调用(原创)
  14. java 审计日志_审计日志的实现
  15. 【编程实践】复杂网络的基本知识及实现
  16. Java标识符、关键字、运算符
  17. MATLAB图形用户界面设计(GUI)
  18. 掌握这10条精进原则,成为一名更好的软件开发者
  19. 干货 | 图解算法、网络编程等,统统都有!
  20. UINO优锘科技:一台物理发动机带你看懂数字孪生八要素

热门文章

  1. Fiddler抓包-app代理配置
  2. Microsoft Word中添加MathType选项
  3. iPhone SE Plus:处理器依旧采用A13芯片?
  4. GlusterFS常用命令集
  5. 阿里云发布智能账单功能,让你免费拥有专属的“云产品 消费账单管家”
  6. Linux下nvidia压力测试,一种服务器linux系统下GPU压力测试的监控方法与流程
  7. execl 单元格颜色设置
  8. 通过对微信7.0的更新分析发现 ″时刻视频″不简单~
  9. C语言——数组、字符串处理函数、strlen、strcpy和strncpy、strcat和strncat、strcmp和strncmp
  10. 架构三问【1】:架构规划 如何撑起数字化转型