Win32 API中内存的申请与释放

本文深入探讨了Windows API如CoInitializeEx和C运行时函数如stderror的内存管理。stderror在线程结束时释放内存,而CoInitializeEx可能在线程或进程结束时释放内存。内存分析显示,一些内存分配在特定条件下才会被清理,如线程相关数据在线程结束时,而某些COM接口的内存可能在进程退出时释放。由于缺乏源码,部分细节无法确定,但整体揭示了Windows API和C运行时内存管理的特点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

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

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

stderror

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

4 allocate: addr = 0000017BD340F610, size = 186
  00007FF67B08F81B  _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.");
        else
                errmsg = 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 _DEBUG
        extern 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 = 256
  00007FFAEE9DCF76  error: 487, bRet = 1
  00007FFAEF8F1AD0  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%准确。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Simple Simple

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值