如何避免SIGSEGV

如何避免SIGSEGV
良好的编程习惯永远是最好的预防方法。良好的习惯包括:
尽量按照C标准写程序。之所以说是尽量,是因为C标准有太多平台相关和无定义的行为,而其中一些实际上已经有既成事实的标准了。例如C标准中,一个越界的指针导致的是无定义的行为,而在实际情况中,一个越界而未解引用的指针是不会带来灾难后果的。借用CU的一个例子,如下:
  1 #include <stdio.h>
  2 #include <stdlib.h>
  3    
  4 int main () {
  5     char a[] = "hello";
  6     char* p;
  7        
  8     for ( p = a+5; p>=a; p-- )
  9         printf ("%c\n", *p);
 10
 11 }
虽然循环结束后,p指向了数组a前一个元素,在C标准中这是一个无定义的行为,但实际上程序却是安全的,没有必要为了不让p成为一个野指针而把程序改写为:
  1 #include <stdio.h>
  2 #include <stdlib.h>
  3    
  4 int main () {
  5     char a[] = "hello";
  6     char* p;
  7        
  8     for ( p = a+5; p!=a; p-- ) {
  9         printf ("%c\n", *p);
 10     }
 11     printf ("%c\n", *p);
 12 }
当然,或许世界上真有编译器会对“越界但未解引用”的野指针进行处理,例如引发一个SIGSEGV。笔者无法100%保证,所以大家在实践中还是各自斟酌吧。
彻底的懂得你的程序。和其它程序员不同的是,C程序员需要对自己的程序完全了解,做到精确控制。尤其在内存的分配和释放方面。在操作每一个指针前,你都应该清楚它所指向内存的出处(栈、堆、全局区),并清楚此内存的生存周期。只有明白的使用内存,才能最大限度的避免SIGSEGV的产生。
大量使用assert。笔者偏好在程序中使用大量的assert,凡是有认为不该出现的情况,笔者就会加入一个assert做检查。虽然assert无法直接避免SIGSEGV,但它却能尽早的抛出错误。离错误越近,就越容易root cause。很多时候出现SIGSEGV时,程序已经跑飞很远了。
打开-Wall  –Werror编译选项。如果程序是自己写的,0 warning应该始终是一项指标(0 warning不包括因为编译器版本不同而引起的warning)。一种常见的SIGSEGV来源于向函数传入了错误的参数类型。例如:
  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <string.h>
  4
  5 int main () {
  6     char buf[12];
  7     int buff;
  8    
  9     strcpy (buff, "hello");
 10
 11 }
这个例子中,本意是要向buf拷贝一个字符串,但由于有一个和buf名称很相近的buff变量,由于一个笔误(这个笔误很可能就来自你编辑器的自动补全,例如vim的ctrl – p, ctrl – n),strcpy如愿的引发了SIGSEGV。实际在编译期间,编译器就提示我们warning: passing argument 1 of `strcpy' makes pointer from integer without a cast,但我们忽略了。
这就进一步要求我们尽量使用编译器的类型检查功能,包括多用函数少用宏(特别是完成复杂功能的宏),函数参数多用带类型的指针,少用void*指针等。此例就是我们在2.2节提到的不经意的行为。
少用奇技淫巧,多用标准方法。好的程序应该逻辑清楚,干净整洁,像一篇朗朗上口的文章,让人一读就懂。那种充满晦涩语法、怪异招数的试验作品,是不受欢迎的。很多人喜欢把性能问题做为使用不标准方法的借口,实际上他们根本不知道对性能的影响如何,拿不出具体指标,全是想当然尔。笔者曾经在项目中,将一个执行频繁的异常处理函数用汇编重写,使该函数的执行周期从2000多个机器周期下降到40多个。满心欢喜的提交了一个patch给该项目的maintainer,得到的答复是:“张,你具体测试过你的patch能带来多大的性能提升吗?如果没有明显的数据,我是不愿意将优雅的C代码替换成这晦涩的汇编的。”于是我做了一个内核编译来测试patch,耗时15分钟,我的patch带来的整体性能提升大约为0.1%。所以,尽量写清楚明白的代码,不仅有利于避免SIGSEGV,也利于在出现SIGSEGV后进行调试。
当你的一个需求,标准的方法不能满足时,只有两种可能:1.从一开始的设计就错了,才会导致错误的需求;2.你读过的代码太少,不知道业界解决该问题的标准方法是什么。计算机已经发展了几十年,如果你不是在做前沿研究,遇到一定得用非标准方法解决的问题的机会实在太小了。正如我们经常用gdb跟踪发现SIGSEGV发生在C库里,不要嚷嚷说C库有bug,大部情况是一开始你传入的参数就错了。
 
小结
无论如何我们应该感谢SIGSEGV,是它让我们能在不重启机器的情况下调试程序。相比那些由于内存使用错误而不得不一次又一次重启机器来debug的内核工程师,SIGSEGV让我们的生活变得轻松。理解SIGSEGV同时,我们也更加理解程序。希望这篇文档对初学C语言的同志有些许帮助。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值