![]()
观察_errno的代码. 该函数首先调用_getptd_noexit()函数. 该函数的代码如下:
[cpp: 第一行[562]]查看纯文本
/ * tiddata.c-_getptd_noexit()实现(__flsindex))== NULL){#else / * _ M_IX86 * / if((ptd = FLS_GETVALUE(__ flsindex))== NULL){#endif / * _ M_IX86 * // **此线程的线程数据结构. 尝试创建一个. * /#ifdef_DEBUGexternvoid * __ cdecl_calloc_dbg_impl(size_t,size_t,int,constchar *createthread内存泄露,int,int *); if((ptd = _calloc_dbg_impl(1,sizeof(struct_tiddata),_ CRT_BLOCK,__ FILE __,__ LINE __,NULL))== else / * _ DEBUG * / if((ptd = _calloc_crt(1,sizeof(struct_tiddata) ))!= NULL){#endif / * _ DEBUG * / if(FLS_SETVALUE(__ flsindex,(LPVOID)ptd))))(/ ** Initializeofper -threaddata * / _ initptd(ptd,NULL); ptd-> _ tid = GetCurrentThreadId(); ptd-> _ thandle =(uintptr_t)(-1);)else(/ ** ReturnNULLtoindicatefailure * / _ free_crt(ptd); ptd = NULL;}))SetLastError(TL_LastError); return(ptd);}
_getptd_noexit()函数首先通过TLS查找与线程相关的数据. 如果找不到,它将分配一块内存,存储_tiddata结构,并将该内存与__flsindex关联. 可以看出errno确实使用TLS技术,并且通过查找_getptd_noexit(),可以发现VC运行时库中的许多函数都使用TLS,errno只是一个典型的函数.
可以猜测,当使用CreateThread函数创建线程时,该线程函数未初始化C运行时库,并且当该线程函数调用errno或localtime或其他需要TLS支持的函数时,这些函数将调用_ getptd_noexit()函数初始化VC运行时库的TLS数据. 当线程功能退出时,该内存不会自动释放createthread内存泄露,因此会发生泄漏.
查看_beginthread / _beginthreadex函数,看看这两个函数如何处理TLS数据:

以_beginthread函数为例. 上面显示的简化代码,整个过程如下:
1. 分配一块内存ptd来存储_tiddata并将其初始化. 用户指定的线程函数和参数存储在ptd
2. 创建一个线程,该线程的启动函数是_threadstart函数,参数是ptd
3. 将ptd设置为TLS数据后_threadstart函数调用_callthreadstart()函数
4. _callthreadstart()函数调用用户指定的线程函数,传入用户指定的参数,然后调用_endthread()函数.
5. 在_endthread()中,调用_freeptd(ptd)释放在步骤1中分配的ptd.

您可以看到以下内容:
1. 使用_beginthread(ex)的原因是此函数对CRT的TLS数据执行适当的分配和重新分配,以避免内存泄漏.
2. 内存泄漏的原因是CreateThead创建的线程不会检查是否需要释放CRT的TLS数据.
正确处理
了解了CreateThread导致的内存泄漏的原因之后,我简要地考虑了避免此类内存泄漏的方法.
首先,请诚实使用_beginthead(ex)函数,这是最安全的方法.

第二,您可以避免在CRT中使用依赖TLS的功能吗?
也许,但是我们编写的代码并不完全是我们自己使用的,由我们的CreateThead创建的线程不一定运行我们自己的代码. 例如,我们提供了一个供其他人使用的库. 您是否必须指定不允许使用errno / localtime等功能?因此,不建议使用此方法.
如果我要使用CreateProcess,或者如果我使用的基础库使用CreateProcess函数,那么我将不可避免地使用依赖于TLS的VC运行时库函数. 有什么方法可以确保释放ptd?
我们可以自己发布ptd. 先前的分析表明_endthread()函数调用_freeptd(ptd)来释放ptd,因此我们可以程函数的末尾调用_endthread()或_endthreadex(retcode)来释放ptd. 查看_freeptd函数的代码后,我发现如果传递的参数为NULL,则_freeptd函数释放调用者线程的ptd,因此您也可以直接调用_freeptd进行清理.
此外,我们可以自动释放ptd. 在VC的项目属性中,可以选择运行库的类型,如图所示:


如果选择/ MTd或/ MDd,则会动态链接运行时库,也就是说,我们的程序将使用传奇的msvcrt * .dll. 在DLL入口函数中,为DLL_THREAD_ATTACH上的附加线程初始化TLS数据,并为DLL_THREAD_DETACH上的分离线程调用_freeptd函数以执行清理. 因此,如果使用VC的动态库,则使用CreateThread和_beginthread同样安全.
总而言之,有几种方法可以避免CreateThread引起的泄漏:
1. 使用_beginthread / _beginthreadex函数创建线程
2. 程函数返回之前,显示_endthread / _endthreadex函数.
3. 在返回线程函数之前,显示了已调用_freeptd(NULL). 此方法在C语言中有效.
4. 使用/ MTd或/ MDd参数
补品
如果没有特殊原因,请不要使用TerminateThread函数终止线程. 在链接静态库的情况下,TerminateThread将导致线程直接终止,因此没有释放ptd的机会. 在链接静态库的情况下,仅程正常退出时才会生成DLL_THREAD_DETACH,因此终止线程的ptd也不会被释放.
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/jisuanjixue/article-154735-1.html
我们的小王子
对啊