之前一直用VS 2012来调试C/C++代码,F五、F十、F11用起来甚是顺手,前面也写过一篇关于VS最好用的快捷键:Visual Studio最好用的快捷键(你最喜欢哪一个), 因此对于调试C/C++代码我一直钟情于VS。可最近下载了一个linux环境下用C++编写的开源库,准备进行一番研究,因为我对gdb调试只处在初步 阶段,尚未对整个项目用gdb调试过,并且gdb调试看起来也不方便,仍是VS看的直观。为了省懒和省时间就将代码弄到VS中进行编译调试,结果发现编 译不成功,由于里面出现了不少相似int block[2*n];这样的变长数组。你们知道传统C语言和C++是不支持变长数组功能的,不过在C99标准中新增的一项功能就是容许在C语言和C++ 中使用变长数组,节省了不少资源。可恨的是,微软的编译器跟不上时代的步伐,C++11都出来这么久了,微软到如今连C99还不彻底支持(不知道最新版的 VS 2013支不支持),不知道是故意而为之仍是其它什么缘由。既然VS不支持变长数组,我这程序就调试不了,我也不可能一个个的把它改为定长的。后来想到用 Eclipse CDT进行调试,就下载了个完整的Eclipse CDT(没在已有的Eclipse上安装CDT插件而是下载了个彻底用于C/C++开发的Eclipse,由于配置插件出现了不少问题,至今还待解决)。 Eclipse中的C/C++库支持使用的是最新版的Cygwin,最新的g++确定是支持变长数组的,这时也发现我下载的库的原做者也是在 Eclipse CDT下开发该项目的,由于工程目录下有.cproject和.project这两个文件,所以认为在Eclipse CDT下编译调试该工程是最佳选择。通过尝试,编译是经过了,但是运行时总是出现这样一个错误:No source available for "ntdll!ZwWriteFile() at 0x77a4133a"。linux
而后各类google、百度都搜不到相关的信息或只有少数几个没什么价值的信息。看来只能断点调试了,发现了问题所在位置:编程
if(i!=0){
re[i]='\0';
if (re[0]!= '#'){
j++;
if (j>=from && (to==-1 || j<=to)){
if (DEBUG) fprintf(stdout,"\n%d) processing regex:: <%s> ...\n",j,re);
parse_re(nfa,re);
}
}
free(re);
}
if (DEBUG) fprintf(stdout, "\nAll RegEx processed\n");
if (re!=NULL) free(re);
//handle -m modifier
if (m_modifier && (!anchored->get_epsilon()->empty() || !anchored->get_transitions()->empty())){
non_anchored->add_transition('\n',anchored);
non_anchored->add_transition('\r',anchored);
}
// delete non_anchored, if necessary
if(non_anchored->get_epsilon()->empty() && non_anchored->get_transitions()->empty()){
nfa->get_epsilon()->remove(non_anchored);
delete non_anchored;
}else{
non_anchored->add_any(non_anchored);
}
发现每次判断该条件语句if (m_modifier...)事后才报上面那个错误,因此坚信是这条语句有问题,通过一番检查以为这语句没啥问题,无奈之下干脆将两个判断条件所有注释掉了,结果仍是出现问题,问题转到注释语句的下面,实在不清楚是啥缘由,就仔细看了下“No source available for "ntdll!ZwWriteFile() at 0x77a4133a"这 条错误语句,发现是和ntdll库有关,因而就搜ntdll库错误相关的资料,最终发现多是跟堆相关,可仍是没能解决问题。最终我仍是转到VS下面调 试,固然前提是去掉了变长数组(还好发现变长数组只出如今两个文件的两个函数中,直接注释掉了),编译成功后运行出现错误:数组
点Continue接着出现错误:编辑器
看了下错误信息真的是堆问题,调试下发现是这句if (re!=NULL) free(re);执行不了,再次调试发现前面re这个对象已经经过free(re)释放了,这里按理说re应该为NULL了也就是不会再次 free(re)了啊,但是实际运行的确re不为NULL所以再次free了re,至关于一块原本已经释放了的内存空间再次被释放,确定会出现堆错误了。 将该条件语句注释掉后,运行成功,而后在Eclipse下注释掉该句也是运行成功。如今问题就来了:函数
我一直认为将一个对象free事后该对象就为NULL了,这样就能够经过判断该对象是 否为NULL来知道该对象是否为正确的释放了,若是没有释放(上面的代码中也就是if(i!=0)没执行)那么在此进行释放以免内存泄露。这个工程库中也是这样作的,但是经过调试却发现不是这样的状况,如今我能想到的惟一解释就是:free(re)事后re所指内存空间的确被释放了,但re自己的值不会改变,也就是形参的值没有改变,因此re仍是原来的值固然就不是NULL了,这样后面的再次free也就会被执行,但re所指的内存已经被释放因此再次 free也就失败了。后来通过网友@hazir的解释,知道了原来是“野指针”的问题,即:若指针p被free或者delete以后,p并无置为NULL,让人误觉得p是个合法的指针,别看free和 delete的名字(尤为是delete),它们只是把指针所指的内存给释放掉,但并无把指针自己干掉,此时指针成为了“野指针”,指向的就是“垃圾”内存。知道了缘由事后,那么之后怎么判断re所指的内存是否被释放了呢?固然上面的代码很好解决,直接在if(i!=0)后面加 else{ free(re); }也就解决了,但是其它状况呢?为了防止“野指针”的产生,编程最佳实践都建议:释放后的指针应当即将指针置为NULL,例:测试
free( p ); if ( p != NULL ) p = NULL;
可是并非每一个野指针再次free事后都会报错的,只要下次有另外一个指针分配内存且以这个地址为起始地址开始分配,那么free就不会报错,具体看下面的测试片断:
google
#include <stdio.h> #include <stdlib.h> void main() { int *pint1, *pint2; pint1 = (int *) malloc (12); printf("pint alloc at : %p\n", pint1); //free pint1, and pint1 do not change to NULL free(pint1); printf("after free pint, and pint is :%p\n", pint1); //pint2 alloc the same start address with pint1 pint2 = (int *) malloc (12); printf("pint2 alloc at : %p\n", pint2); //free pint1 again, not pint2, and not occur error!!! free(pint1); }
运行结果以下:spa
足以说明野指针的使用是不肯定的,因此为了避免出现bug仍是遵照我上面所说的最佳实践作法吧。.net
Eclipse的调试功能也十分强大,但是这里的调试却不友善,一个是错误信息看不 懂,一个是出错位置调试不出来,虽然出错位置就在调试出来的位置的正上面,但调试的时候if (re!=NULL) free(re);这句的确是执行成功了,因此也就不会认为是这句的问题,难道程序真正的出错位置是在Eclipse下调试出来的出错位置的正上面吗?额,应该不会吧。插件
下面不得不简单比较下VS和Eclipse调试功能的差别:
1. 首先若是你习惯了用VS的调试,那么转到Eclipse下可能会有些不太习惯,尤为是你们熟知的VS下的F五、F十、F11到了Eclipse下却变成了F八、F六、F5,其它的也不一样,这样的转变有时候真不习惯。
2. 我以为Eclipse下调试有一点的确比VS好,就是对函数的智能提示,Eclipse下当你讲鼠标放到一个自定义函数上面,会自动显示该函数的实现,而VS下只能显示该函数的声明,要知道定义还得按F12跳过去。
Eclipse下:
VS下:
其它的我就很少做比较了,好比快捷键方面,由于对VS快捷键较熟,对Eclipse快捷键还不是很了解(虽然本身最熟的语言是Java,但调试Java的次数较少),因此二者快捷键方便的差别性我也就不太清楚了,若是清楚的麻烦告诉我。
好了,以本身亲自调试的一 个小错误引出了这么一个问题:Eclipse与VS,你更喜欢哪一个呢?固然有人会说,开发C/C++与C#就用VS,开发Java就用Eclipse,可 是Eclipse可不只仅是Java的编辑器,Eclipse是全能型的,能够编译常见的全部语言如C/C++、C#、Python、Ruby等等,若是 你钟爱Eclipse,彻底能够用它来开发你想要开发的任何程序。