使用未初始化的指针变量及段错误(segmentation fault)分析

就C/C++语言而言,一个良好的编程习惯是在使用变量之前对变量进行初始化操作。若不对变量进行初始化就使用它们,在未来的某个时刻可能会引发一系列难以察觉的错误,严重的将导致程序的崩溃。

1.变量未初始化

相比于普通变量,指针变量未初始化就直接使用更容易导致错误,常见的错误是段错误(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设置的。例如,我的操作系统下的默认配置文件内容如下:

其中,用来个性化命名规则的参数的意义如下:

那么,如果我想在/data/coredumped/目录下以“core-运行的程序名-程序pid-core产生的时间”来个性化core文件名,则只需要运行如下命令即可:
$ echo “/data/coredumped/core-%e-%p-%t” > /proc/sys/kernel/core_pattern

3.示例+gdb调试

(1)示例

例如,有如下源程序:一个未初始化的指针变量,作为函数的实参传入函数中,引发段错误,并产生一个core文件

编译、运行:

同时产生了core文件——core.2120

(2)core文件调试

使用如下命令进行调试:gdb -c [core文件的路径名] [待调试程序]
(关于gdb的基本使用,可以参考我的另一篇文章http://dulishu.top/linux-gdb/

比如,对segmentFault进行core文件调试:

其中,段错误出现在了第3行。
接下来,使用bt(backtrace)回溯命令进行堆栈跟踪,查看堆栈情况:

其中,#0表示最近的一次堆栈调用情况,调用的是函数f00(p=0x0),并且,foo函数的参数指向的地址为0x0,是一个无效的地址。因此,引发了段错误。

建议:针对该示例程序,有两种修改方法。一是,声明了指针变量score之后,为其动态分配一块空间之后再传入函数foo中;二是,不声明指针变量score,改为声明普通变量sc,并以取地址的方式传入函数foo中——foo(&sc);

最后,铭记:“拒绝段错误,从指针变量未初始化之前一定不要使用开始做起。”

发表评论

电子邮件地址不会被公开。 必填项已用*标注