突然,目光停留在了一行看上去再平常不过的代码.
一个子函数的调用:fun(str);
该函数的原型是
void fun(std::string xxxx).
问题居然就出在这个让人觉得再可靠不过的函数身上.
因为,在调用此函数过程中,行参需要被创建,COPY.然而string函数内部是会使用堆内存来存储字符的.在平时他很安全,因为他肯定会被析构,删除堆内存.然而在子线程非正常退出的情况下,就完全不同了.线程的非正常退出,只会删除string类本身所占的内存,也就是出栈,然而string的析构函数此时却不会被调用到了,因而导致在堆中创建的内存无法被正确删除.
由此想到,任何会创建堆内存的类,在子线程中都是危险的.几乎包括std,MFC中绝大多数类,对象.
就是简单的一句
CString str = "hello, world!", 在子线程中居然也成了极度危险任务.
解决的方法只有一个,不用,或者使用外部主线程中的这类对象.子线程函数参数一定要使用指针或者引用.
例如,上面的函数改成
void fun(const string&) 或者 void fun(string&)
回头看了一下windows核心编程中关于线程非正常退出的说明,这次有了更深的体会
按说一般局部变量是分配在stack上的,不会内存泄漏;
但是这个Cstring类型的变量就特殊了,“该管理器从进程堆(在 ATL 中)或 CRT 堆(在 MFC 中)分配内存。”既然分配在堆上,那就要回收。默认是到了该变量生存期结束的时候有管理器回收,但是如果你强行 ExitThread(0); 或者exit(0),让该线程“不得好死”,自然就内存泄漏按说一般局部变量是分配在stack上的,不会内存泄漏;
但是这个Cstring类型的变量就特殊了,“该管理器从进程堆(在 ATL 中)或 CRT 堆(在 MFC 中)分配内存。”既然分配在堆上,那就要回收。默认是到了该变量生存期结束的时候有管理器回收,但是如果你强行 ExitThread(0); 或者exit(0),让该线程“不得好死”,自然就内存泄漏
std::string也一样
如果多线程编程中,多个线程用到了一个全局的CString, 这样是很危险的。
因为CString不是线程安全的,
CString只保证类级的线程安全,
要做到对象级别的线程安全,需要你自己进行同步,
也就是说,
可以同时有N个线程在读,
但是写的时候,必须保证没有任何线程"正在"读和写
才可以写入.
CString str;
CCriticalSection cs;
cs->Lock( );
str+="abcdefg";........
do anything you want
cs->Unlock( );
多线程中CString内存泄漏的解决方法。
多线程导致的内存泄漏
DWORD WINAPI ConnectionWorkerProc(LPVOID pObject)
{
CString strPath;
CString strFileName;
CString currentStr;
TCHAR currentPath[512] = _T("");
TCHAR sendPfilePath[256] = _T("");
GetCurrentDirectory(sizeof(currentPath), currentPath);
strPath=CString(currentPath);
currentStr=CString(currentPath);
strFileName=strPath + strFileName;
CString strTest;
char test[30] = "sasdsdsaasd";
strTest = CString(test);
while{
Sleep(30);
}
}
f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\strcore.cpp(141) : {1500} normal block at
0x01A7D220, 40 bytes long.
Data: <, x > 2C FB BF 78 0B 00 00 00 0B 00 00 00 01 00 00 00
f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\strcore.cpp(141) : {1498} normal block at
0x01A7E340, 46 bytes long.
Data: <, x > 2C FB BF 78 0E 00 00 00 0E 00 00 00 01 00 00 00
f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\strcore.cpp(141) : {1497} normal block at
0x01A7DAB8, 46 bytes long.
Data: <, x > 2C FB BF 78 0E 00 00 00 0E 00 00 00 01 00 00 00
f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\strcore.cpp(141) : {1496} normal block at
0x01A7CCD0, 46 bytes long.
一直到程序运行结束,线程函数都没有结束,在栈上没有弹出,导致了内存泄漏。
在转化和赋值的过程,CString内部分配了内存,但是由于该函数一直没有执行结束,CString内部申请的
内存便一直没有释放掉。
解决的方法:
1,当知道线程有可能或者确定不会结束,不要在线程中使用CString的copy,assignment,add,使用TCHAR
或者char的,strcat,strcpy等,或者使用std::string,也不会造成内存泄漏
2,使用CString的时候,new和delete,CString *pStr = new CString;用完了之后delete,也可以避免
我们用MFC开发时经常会用到CString类,无可否认,CString类是很好用,但很少人注意到CString类不是线程安全的。一般地,界面编程都是在主线程,很少用到多线程,所以不会遇到什么问题。但是,当我们多个线程同时操作同一个CString类型变量时,就可能会出现内存地址错误,最终导致进程异常退出。内存错误导致的问题也很难调查,通常导致内存错误的地方没有马上报异常,而且在程序的其他地方才捕获异常。
CString类的Debug版本和Release版本不完全一样,Debug版本则直接分配(MFC在Debug版本有内存管理,主要是为了排错,内存泄漏等),CString类在Release版本会使用定长内存管理(CFixedAlloc类),主要管理是4个长度的内存,如下:1AFX_STATIC CFixedAlloc _afxAlloc64(ROUND4(65*sizeof(TCHAR)+sizeof(CStringData)));
2AFX_STATIC CFixedAlloc _afxAlloc128(ROUND4(129*sizeof(TCHAR)+sizeof(CStringData)));
3AFX_STATIC CFixedAlloc _afxAlloc256(ROUND4(257*sizeof(TCHAR)+sizeof(CStringData)));
4AFX_STATIC CFixedAlloc _afxAlloc512(ROUND4(513*sizeof(TCHAR)+sizeof(CStringData)));这样做应该是防止内存碎片和提高效率,由于CString类都会重用分配的定长内存,所以一般异常的地方大多数也是在CString操作的地方。有兴趣可以看看CString类的实现。
避免这样的问题最简单的办法就是加锁或者不用CString类。加锁用临界区就可以,实现比较简单,在这里不多说。