刚开始学c的时候,最头疼的事情是编译总是通不过,郁闷的要死,只要编译通过了,就兴奋的要死。现在,最头疼的事情是什么呢,编译没问题,但是程序跑的时候会出现Segmentation fault!
这个东西非常的恼人,在还是菜鸟阶段不会用gdb的时候,实在解决不了就放弃了,或者用一些对内存管理没有要求的语言来曲线救国了,但是自从开始接触了gdb这类的调试语句之后,发现了出现
Segmentation fault的一些情况,以及解决的方法,写出来总结下:
Segmentation fault 本质就是内存错误,在c/c++语言里,就是指针错误,这个错误产生的原因是你错误的使用了内存,访问了你不该访问的东西,常见的可归类如下:
1.数组越界,你定义了一个大小为10的数组array[10],但是c是从0开始计数的,最后的数组元素是array[9],然后你去访问了a[10],这样就读到了一块不属于你的内存,结果出现了Segmentation fault.
当然,这个是最简单的错误。注意点就会解决。
2.指针的问题。指针本质就是一个数字,IA32下就是一个4字节32位的数字,类似0xAB0011FF这个样子的,如果是x86-64位下的话,就是类似于0xAABBCCDDEEFF0011这种类型8字节64位的,这个数字表示
的是一个内存地址,计算机根据这块内存地址找到内存为此地址的内容,根据其类型读取。这里特别说一下NULL指针,在gdb里,你可以看到NULL实际上就是0x0,当然,前面有其类型,比如(void*) 0x0,这个样子的,
所以,如果你解引用这个NULL指针,其实就是要求计算机去访问内存为0的地址,结果可想而知。当然,有些人觉得自己没蠢到去解引用这个地址,当然,通常情况下这个错误不是这么直接的,但是本质确是一样的,我
举个具体的例子你感受下:
void changept(int* cpt){
cpt=0x48;
printf("in function,the pointer is: %x\n",cpt);
}
int main(){
int *s=0x20AB;
*s=0x332A;
printf("before calling function,the pointer is: %x\n",s);
changept(s);
printf("after calling function,the pointer is: %x\n",s);
return 0;
}
能想像出答案是什么吗,在函数changept中,我们改变了指针cpt 的值,注意是指针本身的值,或者说是这个指针需要指向的地址,但是在函数调用之后,指针显示的地址还是没有改变,但是在函数调用中,我们发现指针的值变了。这当然是因为c语言本身的一个特性,函数是传值调用,你传进去一个指针,注意这个指针是个数字,比如例子中的0x20ab,结果函数在调用时复制了这个值给局部变量(这个局部变量是同类型指针),然后局部变量获得了0x20ab这个地址,接下来让局部变量去指向了新的地址0x48,函数调用结束后这个指针消失,但是原来的指针确还是指向原来的值。好,问题就出在这里,如果我要在函数中为这个指针分配一个内存,比如cpt=(int*)malloc(sizeof(int)*10);注意,从堆上分配的内存让这个局部指针指向了另一个地址,作为参数的指针实际上还是悬空的,没有指向新开辟的地址,如果程序员想用这个悬空指针(他可能认为已经获得了内存)去访问这个开僻的新内存,那么实际上他访问的还是原来的内存,这样就会造成指针的错误引用,造成Segmentation fault这样的错误。所以,不要在函数中改变指针本身的值
解决上述问题的方法很简单,把函数changept的参数改成(int** cpt),*cpt=0x48,调用的时候就是changept(&s). 结果就正确了. 为啥呢,这个很简单,你传进去的是存储这个指针的内存地址,也是个数字,比如0x88, 0x88所指向的内存空间里存储着这个指针0x20AB,然后呢,局部变量获得了0x88这个地址,也指向了指针0x20ab,解引用0x88,获取0x20ab的值,并改为0x48, 此时指向0x20ab的指针已经被修改了,于是函数调用后的结果也变了。这个时候,假如要为原来指向0x20ab的指针申请了新的地址,那么指针也就指向了新的地址,这个时候访问这段开辟出的地址就没有问题了。所以,要想改变指针的值,要给函数传入这个指针的地址,或者说是指向指针的指针。
3. 数值类型,尽量不要用unsigned这类无符号类型的整数,比如你申请一块地址,malloc(sizeof(int)*a),如果a=-1的话(IA32 下是 补码 表示0xFFFFFFFF,unsigned a 会导致这个数变成一个超大的数,当然这个数还是0xFFFFFFFF,但是作为无符号来解读的话就是一个超大的数了,是2^32-1这样一个数,堆上分配不出这么大的空间,导致程序出现意想不到的问题。此外,如果你的程序在一个循环内,如果用无符号的数去和类似负数之类的比,可能产生死循环或者内存越界。
4.字符串,其实主要就是注意,如果你用strlen之类的函数,记得分配内存的时候要加1,因为strlen不包含‘\0'在内,否则还会产生越界,造成segmentation fault.
其实以上问题避免了,应该会杜绝90%以上的段错误,但是,更本质的是,你应该多学习计算机底层的机制,毕竟,c的强大就是它既有高级语言的便捷,也有底层语言的强大,而了解指针也就相当与掌握了c语言的核心,终将帮助你提高自身的编程修养!