就C/C++语言而言,一个良好的编程习惯是在使用变量之前对变量进行初始化操作。若不对变量进行初始化就使用它们,在未来的某个时刻可能会引发一系列难以察觉的错误,严重的将导致程序的崩溃。
1.变量未初始化
相比于普通变量,指针变量未初始化就直接使用更容易导致错误,常见的错误是段错误(segmentation fault)——其根本原因一般是由于非法的内存访问所引起的。
段错误一般是可以通过编译的,在运行时该错误才会显现出来,即运行时错误。例如:
1 2 3 4 5 6 7 |
int main() { int *p; //声明指针变量 *p = 666; //使用未初始化的指针变量 return 0; } |
编译、运行:
1 2 |
[root@localhost c]# ./test Segmentation fault |
所以,为了减少此类错误的发生,有如下建议:
建议1:在使用变量之前,一定要对该变量进行初始化,尤其是指针变量;
建议2:如果需要声明一个指针变量作为函数实参中的传出参数,那么一定要记得声明该指针变量之后为其动态分配内存(当然也要记得在使用完之后释放该内存),否则会因为访问非法的内存地址而导致段错误。
通俗点的讲就是,指针变量未初始化之前一定不能作为函数的实参传给函数。
建议3:除非建议2是必要的,否则不建议声明一个指针变量作为函数实参中的传出参数。可替代的并且更好的做法是,声明一个普通变量,然后对该普通变量取地址后传入函数中。
2.段错误之core文件
(1)ulimit -c
程序运行时产生的段错误往往难于发现和调试,值得庆幸的一点是,产生段错误后操作系统会将该错误记录下来,并生成一个core文件供后续调试之用,从而可以定位段错误的根源。
然而,linux系统默认是不产生core文件的。若想产生core文件,需要进行一些设置。最简单的交互式模式下设置方法是使用如下命令:
$ulimit -c unlimited
而诸如ulimit -c 1024等命令的意思是表示产生的core文件大小不超过指定的字节数。
但这种设置只是临时的,在重启操作系统之后就会失效(即ulimit -c 会被置为0)。若想一劳永逸,长久生效,则需要修改相应的配置文件,在配置文件(/etc/profile或.bashrc或.bash_profile)中加入上述命令,不清楚的同学可以自行查阅其他资料。
(2)core文件相关设置
设置好ulimit -c unlimited之后,在段错误时会生成一个core文件,并且默认情况下此core文件会生成在与该可执行程序的相同目录下。
当然,core文件生成的路径以及生成core文件的命名规则都是可以通过配置文件core_pattern设置的。例如,我的操作系统下的默认配置文件内容如下:
1 2 |
[root@localhost kernel]# cat /proc/sys/kernel/core_pattern |/usr/libexec/abrt-hook-ccpp %s %c %p %u %g %t e |
其中,用来个性化命名规则的参数的意义如下:
1 2 3 4 5 6 7 |
%p - insert pid into filename %u - insert current uid into filename %g - insert current gid into filename %s - insert signal that caused the coredump into the filename %t - insert UNIX time that the coredump occurred into filename %h - insert hostname where the coredump happened into filename %e - insert coredumping executable name into filename |
那么,如果我想在/data/coredumped/目录下以“core-运行的程序名-程序pid-core产生的时间”来个性化core文件名,则只需要运行如下命令即可:
$ echo “/data/coredumped/core-%e-%p-%t” > /proc/sys/kernel/core_pattern
3.示例+gdb调试
(1)示例
例如,有如下源程序:一个未初始化的指针变量,作为函数的实参传入函数中,引发段错误,并产生一个core文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
//filename:segmentFault.c void foo(int *p) { *p = 100; return; } int main() { int *score; foo(score); return 0; } |
编译、运行:
1 2 |
[root@localhost c]# ./segmentFault 段错误 (core dumped) |
同时产生了core文件——core.2120
(2)core文件调试
使用如下命令进行调试:gdb -c [core文件的路径名] [待调试程序]
(关于gdb的基本使用,可以参考我的另一篇文章http://dulishu.top/linux-gdb/)
比如,对segmentFault进行core文件调试:
1 2 3 4 5 6 7 8 9 |
[root@localhost c]# gdb -c core.2120 segmentFault Reading symbols from /root/c/segmentFault...done. [New Thread 2120] Core was generated by `./segmentFault'. Program terminated with signal 11, Segmentation fault. #0 0x0804839a in foo (p=0x0) at segmentFault.c:3 3 *p = 100; Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.166.el6_7.3.i686 (gdb) |
其中,段错误出现在了第3行。
接下来,使用bt(backtrace)回溯命令进行堆栈跟踪,查看堆栈情况:
1 2 3 4 |
(gdb) bt #0 0x0804839a in foo (p=0x0) at segmentFault.c:3 #1 0x080483b4 in main () at segmentFault.c:10 (gdb) |
其中,#0表示最近的一次堆栈调用情况,调用的是函数f00(p=0x0),并且,foo函数的参数指向的地址为0x0,是一个无效的地址。因此,引发了段错误。
建议:针对该示例程序,有两种修改方法。一是,声明了指针变量score之后,为其动态分配一块空间之后再传入函数foo中;二是,不声明指针变量score,改为声明普通变量sc,并以取地址的方式传入函数foo中——foo(&sc);
最后,铭记:“拒绝段错误,从指针变量未初始化之前一定不要使用开始做起。”