首页 > 代码库 > C++内存问题大集合(指针问题,以及字符串拷贝问题,确实挺危险的)
C++内存问题大集合(指针问题,以及字符串拷贝问题,确实挺危险的)
作者:rendao.org,版权声明,转载必须征得同意。
内存越界,变量被篡改
memset时长度参数超出了数组长度,但memset当时并不会报错,而是操作了不应该操作的内存,导致变量被无端篡改
还可能导致内存越界的函数有memset、memcpy、memmove、strcpy、strncpy、strcat、sprintf等等
临时指针问题,std::string、wstring的c_str()是个临时指针
c_str()返回值是个char*/wchar_t*指针,这个数组的数据是临时的,当有一个改变这些数据的成员函数被调用后,其中的数据就会失效。此时,用strlen、wcslen、strcpy、wcscpy等操作都是不可预知的,程序会崩溃。因此要么现用先转换,要么把它的数据复制到用户自己可以管理的内存中。
string s=”1234″;
const char* c = s.c_str();
这样的写法是错误的。因为重复使用的c指向内容实际上很容易失效。
delete char*时程序崩溃
1、内存越界
char* newstr = new char[strlen(s1) + strlen(s2) + 1];
sprintf(newpath, “%s\\%s”, s1, s2);
然后这个newstr在被delete时崩溃了,原因是开辟的内存空间不够,内存越界,无法容纳结束符0,如果把strlen(s1) + strlen(s2) + 1改成strlen(s1) + strlen(s2) + 2就OK了。
2、删除的指针不属于自己的堆栈
如果对于堆来说,每个DLL有自己的堆,那么从DLL中动态分分配的内存最好是从DLL中删除。如果从DLL中分配内存,然后再EXE中或者另外一个DLL中删除,很可能导致程序崩溃。并且,输出报错为Invalid Address specified to RtlValidateHeap。
3、重复delete
重复delete同一个指针会崩溃,有时不易发现,可能的情况之一就是某类存在char*成员(需要new开辟内存),而此类是浅拷贝,若不同对象A、B间发生拷贝,A销毁时释放了char*指向内存,B仍认为自己的char*指针是有效的。有时现象更加隐秘,你晓得有浅拷贝这回事还不够,比如拷贝的发生不一定是你自己的代码中直接导致的,比如vector的push_back操作就会不停的导致类对象的拷贝,而你不了解push_back的本质的话就不晓得发生了拷贝。
4、不同工程使用了不同的运行时库设置
具体现象是delete指针时程序崩溃,并输出提示Invalid Address specified to RtlValidateHeap,这是因为在不同模块(工程)之间传递 C++ 类,而这两个模块用了不同的运行时库(C/C++ -> Code Generation -> Use runtime library)设置。例如:EXE 模块调用 DLL 模块里传递 C++ 类的函数,DLL模块使用静态链接(Release 是Multi-threaded (/MT) 、Debug 是 Multi-threaded Debug (/MTd) )方式编译,而 EXE 模块使用动态链接(Release 是 Multi-threaded DLL (/MD) 、Debug 是 Multi-threaded Debug DLL (/MDd) )方式编译。可以对比这两个模块的Use Runtime Library ,看看设置是否一样,如果不一样要改成一样的。
以上是网络上的解释,而且这个原因可能和第3条有关联,可能是由于运行时库的设置不同,导致调用另外模块的类的方法时,导致这时new出来的内存所属堆归属有问题,所以delete时就出错了。这一段是简单的猜测,很可能猜错了,但这个问题已经消耗了非常多时间了,抓紧,实践证明,我在EXE(Peer)和DLL(Reg)中使用了相同的类(Xstr),并都是源码加入工程编译的,EXE中新建类对象,把对象指针传入DLL,DLL中调用类的某方法(会new一段内存)来容纳返回的数据,而这个类对象在EXE中别销毁时就出现了delete wchar_t*崩溃的问题,提示Invalid Address specified to RtlValidateHeap。此时检查运行时库设置,发现的确不同,而且正如网上摘录中举得例子一样,如果把EXE和DLL中的该设置都改为Multi-threaded DLL/ Multi-threaded Debug DLL,则问题解决。 Multi-threaded与Multi-threaded DLL,前者是指使用多线程版本的运行时库但静态编译,后者的差别就是动态链接。
关于Use runtime library的含义,微软和C有两种C运行期函数库,一种是普通的函数库:LIBC.LIB,不支持多线程。另外一种是支持多线程的: msvcrt.lib。如果一个工程里,这两种函数库混合使用,可能会引起这个错误,一般情况下它需要MFC的库先于C运行期函数库被链接,因此建议使用支持多线程的msvcrt.lib。所以在使用第三方的库之前首先要知道它链接的是什么库,否则就可能造成LNK2005错误。如果不得不使用第三方的库,可以尝试按下面所说的方法修改,但不能保证一定能解决问题,前两种方法是微软提供的: A、选择VC菜单Project->Settings->Link->Catagory选择Input,再在Ignore libraries 的Edit 栏中填入你需要忽略的库,如:Nafxcwd.lib;Libcmtd.lib。然后在Object/library Modules的Edit栏中填入正确的库的顺序,这里需要你能确定什么是正确的顺序.(最好不要这么做)
B、选择VC菜单Project->Settings->Link页,然后在Project Options的Edit栏中输入/verbose:lib,这样就可以在编译链接程序过程中在输出窗口看到链接的顺序了。 C、选择VC菜单Project->Settings->C/C++页,Catagory选择Code Generation后再在User Runtime libraray中选择MultiThread DLL等其他库,逐一尝试。
5、指针所指内存地址不再是new出来的首地址
char* p = new char[10];
strcpy(p, “hello”);
p++;
delete p; //运行到这回崩溃
内存泄露的解决
1、 查找new和delete,malloc/realloc/calloc/strdup和free,VirtualAlloc和VirtualFree是否成对
2、 检查new char之后是否误用了小括号,因为使用小括号的话,是赋初值,方括号才是指定长度。Debug时可能越界执行而不立即报错,但程序在退出时则会提示内存泄露了。当然,如果是release,估计程序执行到这里将立即崩溃。
3、 有时候,VC的内存泄露提示行代码中new出来的类对象出现内存泄露,而实际上检查发现该类对象的的确确是delete了的,实际上是提示不够精确
可能1:实际发现还可能不是该类对象new了未释放,而是该类对象中的代码、包括调用的函数中出现了new但未释放的情况,比如new CmySiteRoot中调用了xml_INIeasyget,而xml_INIeasyget中调用了只new不delete的charenc_strutf8toansi,进而导致内存泄露的,但报错位置却是new CmySiteRoot
可能2:事实证明,甚至可能和报错的new位置new出来的那个东西毫不相干的,此时还是乖乖按照步骤解决方法1去查找,反而快些,基本肯定的是,按照步骤1去找,总可以找到不匹配的地方,不一定过于相信VC的输出提示位置
减少数组越界读写的方法
boost::array(debug时检查数组下标,release时效率和普通数组一样)、智能指针、vector
内存泄露、越界检查工具
BoundsCheck、pageheap、gflags(Windebug中带的)、WinDebug
字符串拷贝注意要点
一定要注意结束符,经过实际验证,strncpy、wcsncpy、memcpy、wcstombs等等函数都只会拷贝指定个数的字符,而不理会是否在末尾有结束符,如果忘记了自己补上结束符,可能导致访问这个字符串时访问到别人的内存喔。
Related Posts:
- boost配置(VC/CodeBlocks)与编译(bjam+msvc/mingW)
Tags: C++, 内存
http://rendao.org/blog/224/
C++内存问题大集合(指针问题,以及字符串拷贝问题,确实挺危险的)