CreateThread导致内存泄露的原因
这得从C运行时库说起了。
VC运行时库,有一个宏errno,用来获得上一步操作的错误码,类似于Win32中的GetLastError()函数。在多线程环境下,不同线程调用errno返回的都是caller线程的错误码,绝对不会混淆,这是因为使用了TLS技术。
TLS,Thread Local Storage,是用来存取线程相关数据的一种技术,在Win32中由操作系统的Tls*系列函数提供支持。例如,可以在程序开始的地方调用TlsAlloc()函数,获得一个TLS index,这个index在进程范围内有效,然后可以创建n个线程,在每个线程中使用TlsSetValue(index,data)将线程相关数据和index关联起来,使用TlsGetValue(index)来获取当前线程和index相关联的的线程相关数据。
查看msdn可以发现,Tls*函数的定义如下:
[cpp] view plaincopy
DWORD WINAPI TlsAlloc(void);
BOOL WINAPI TlsSetValue(
__in DWORD dwTlsIndex,
__in LPVOID lpTlsValue
);
LPVOID WINAPI TlsGetValue(
__in DWORD dwTlsIndex
);
BOOL WINAPI TlsFree(
__in DWORD dwTlsIndex
);
观察TlsSetValue/TlsGetValue的原型可以发现,与index关联的数据只能是void *类型,因此通常的做法是在线程开始的时候,为这个线程分配一块内存,用于存储所有与线程相关的数据,然后把这块内存的起始地址与index关联起来。如果这块内存在线程退出的时候没有释放掉,那就有内存泄露的危险。
回到errno,来看看C运行时库是如何实现errno的。
errno的声明和实现如下:
[c-sharp] view plaincopy
/* error.h - errno的声明 */
_CRTIMP extern int * __cdecl _errno(void);
/* dosmap.c - errno的实现 */
int * __cdecl _errno(
void
)
{
_ptiddata ptd = _getptd_noexit();
if (!ptd) {
return &ErrnoNoMem;
} else {
return ( &ptd->_terrno );
}
}
观察_errno的代码,函数首先调用了_getptd_noexit()函数,这个函数的代码如下:
[cpp] view plaincopy
/* tiddata.c - _getptd_noexit()实现 */
_ptiddata __cdecl _getptd_noexit (
void
)
{
_ptiddata ptd;
DWORD TL_LastError;
TL_LastError = GetLastError();
/*
* Initialize FlsGetValue function pointer in TLS by calling __set_flsgetvalue()
*/
if ( (ptd = (__set_flsgetvalue())(__flsindex)) == NULL ) {
if ( (ptd = FLS_GETVALUE(__flsindex)) == NULL ) {
/*
* no per-thread data structure for this thread. try to create
* one.
*/
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) {
if ((ptd = _calloc_crt(1, sizeof(struct _tiddata))) != NULL) {
if (FLS_SETVALUE(__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);
}
_getptd_noexit()函数首先通过TLS查找线程相关数据,如果没有找到,就分配一块内存,存放_tiddata结构,并将这块内存与__flsindex相关联。由此可见,errno的确使用了TLS技术,而且通过查找_getptd_noexit() 可以发现,VC运行时库中很多很多函数都使用了TLS,errno只不过是其中的一个典型。