有时候程序错误出现了直接崩溃,不知道错误在哪里,编写代码的时候错误处理不好,没有直接输出错误位置。这就造成了麻烦。
如果是MSVC的话,调试模式在崩溃时直接定位到错误位置,MINGW开发的,用GDB调试工具也可以定位错误。
我还有2个通过修改代码查找问题所在的方法:
方法1
选定一个可能的函数的大概范围,不确定什么函数就向上级找。最多找到主函数main。随便插入几个输出语句
void traverse_files_and_dirs_unicode(std::wstring sDir){
if(m_isStopTraverse){
return;
}
std::cout<<"111111111111111111\n";
HANDLE file;
WIN32_FIND_DATAW fileData;
file = FindFirstFileW((sDir+L"*").c_str(), &fileData);
if (file != INVALID_HANDLE_VALUE)
{
std::cout<<"222222222222\n";
if(wcscmp(fileData.cFileName,L".") == 0||wcscmp(fileData.cFileName,L"..") == 0){
;
}else if(fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY){
std::string str324 = UnicodeToUTF8(sDir+fileData.cFileName+L"\\");
m_dirProcesser(str324);
traverse_files_and_dirs_unicode(sDir+fileData.cFileName+L"\\");
}else{
std::string str325 = UnicodeToUTF8(sDir+fileData.cFileName);
m_fileProcesser(str325);
}
bool bState = false;
bState = FindNextFileW(file, &fileData);
while(bState&&!m_isStopTraverse){
std::cout<<"3333333333333\n";
if(wcscmp(fileData.cFileName,L".") == 0||wcscmp(fileData.cFileName,L"..") == 0){
;
}else if(fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY){
std::string str326 = UnicodeToUTF8(sDir+fileData.cFileName+L"\\");
m_dirProcesser(str326);
traverse_files_and_dirs_unicode(sDir+fileData.cFileName+L"\\");
}else{
std::string str327 = UnicodeToUTF8(sDir+fileData.cFileName);
m_fileProcesser(str327);
}
bState = FindNextFileW(file, &fileData);
std::cout<<"44444444444444\n";
}
}else{
;
}
std::cout<<"55555555555555555\n";
FindClose(file);
}得到下面的输出,没有完整的输完插入的语句就报错,说明找对了。
55555555555555555
44444444444444
3333333333333
44444444444444
3333333333333
44444444444444
3333333333333
terminate called after throwing an instance of 'std::logic_error'
what(): basic_string::_M_construct null not valid
--------------------------------
Process exited after 10.18 seconds with return value 3
Press ANY key to exit...
在[3]后面卡住,之后在原先[3]和[4]之间插入更多语句
while(bState&&!m_isStopTraverse){
std::cout<<"1111111111\n";
if(wcscmp(fileData.cFileName,L".") == 0||wcscmp(fileData.cFileName,L"..") == 0){
std::cout<<"222222222222\n";
}else if(fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY){
std::cout<<"3333333333333\n";
std::string str326 = UnicodeToUTF8(sDir+fileData.cFileName+L"\\");
m_dirProcesser(str326);
traverse_files_and_dirs_unicode(sDir+fileData.cFileName+L"\\");
std::cout<<"4444444444444\n";
}else{
std::cout<<"55555555555\n";
std::string str327 = UnicodeToUTF8(sDir+fileData.cFileName);
m_fileProcesser(str327);
std::cout<<"66666666666666\n";
}
bState = FindNextFileW(file, &fileData);
std::cout<<"777777777777777\n";
}输出表示在[3]后面崩溃,没有运行到[4],中间只有2个执行的语句,递归自己的不算。
66666666666666
777777777777777
1111111111
3333333333333
terminate called after throwing an instance of 'std::logic_error'
what(): basic_string::_M_construct null not valid
--------------------------------
Process exited after 10.55 seconds with return value 3
Press ANY key to exit...
最后是发现m_dirProcesser(str326);这个函数的问题,之后再进入函数,再用这个办法找。
最后在这个函数里就是一个小问题,使用了g_locale_to_utf8转换字符编码,因为之前自己调整了一部分函数编码,造成这个函数接收的字符串本来就是utf8编码,再次转换会返回空指针,std::string赋值就造成了崩溃,直接去掉这个代码就正常了。
gchar *utf8Char = g_locale_to_utf8(dirPath.c_str(),-1,0,0,0);
std::string dirPathUTF8 = utf8Char;
g_free(utf8Char);这种查找过程非常简单。
方法2
屏蔽模块:屏蔽就是直接去掉对该模块的使用,或者是编写一个绝对不会出错的简单的欺骗模块,没有实际功能的模块,对一些模块进行替换。
比如一个函数A,进行了一些计算返回一个值。
直接用一个返回固定值的欺骗模块B进行替换。
在调用的时候,就等于对模块A屏蔽了
1.直接屏蔽一个或者多个模块的执行,如果正常了,就说明那些模块有问题。如果不正常,屏蔽更多的模块。
2.屏蔽到运行正常之后,缩小屏蔽的模块范围,最后定位到1个模块,这个模块屏蔽了就正常,不屏蔽就崩溃,之后进入那个模块。
3.进入那个模块。继续按照上面的办法屏蔽里面的内容,最后问题范围会缩小到一个很小的范围,几条代码,几个变量,肯定就知道问题原因了。
这个办法也是很有用的,我叫他问题屏蔽法。
总结
要做好错误处理,检查空指针,用try...catch块,在处理错误的时候要输出错误位置。
如果我当初这样写就不会崩溃了,直接打印错误结果和位置。
gchar *utf8Char;
std::string dirPathUTF8;
utf8Char = g_locale_to_utf8(dirPath.c_str(),-1,0,0,0);
if(NULL!=utf8Char){
dirPathUTF8 = utf8Char;
g_free(utf8Char);
}else{
std::cout<<"Error:"<<__FILE__<<" "<<__LINE__<<std::endl;
}Error:D:/ProjectSpace/reddevcpp/FileMemory/memorycreater.cpp 43
通用问题查找
根据我的经验,c/cpp程序运行崩溃。通常就是这些情况:
- 内存缓冲区不足,数组越界,导致溢出
- 使用已经释放了的野指针
- 实例未初始化,使用了空指针
- 对同一个指针的二次释放
- 未捕获的异常抛出
- 多线程竞争变量,没有处理好线程同步
- 迭代器失效
- 动态库版本不匹配