背景
在使用MPI并行化矩阵乘向量的实验中,发现如果矩阵的规模规模较大时,就会导致段错误。
将错误代码进行简化,如下所示
/*
File name: seg_error.c
How to compile: gcc -g -Wall -o seg_error seg_error.c
*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(){
int m, n;
double *a;
printf("Please input m and n: ");
scanf("%d%d", &m, &n);
a = malloc(m * n * sizeof(double));
memset(a, 0, sizeof(double) * m * n);
free(a);
return 0;
}
输出如下所示
Debug
之前在找段错误的bug时,我一般是先使用printf来确定引发段错误的代码,然后再进一步寻找引发段错误的原因。但是最近我发现使用GDB
可以更加快速地找到引发段错误的代码,尤其是当程序较大时。
使用GDB调试的过程如下所示:
- 首先使用gcc编译,需要加上-g选项:
gcc -g -Wall -o seg_error seg_error.c
- 然后直接使用gdb运行seg_error:
gdb ./seg_error
- 进入gdb后,输入
r
开始运行程序 - 程序会一直执行直到发生段错误,此时输入
where
即可找到引发段错误的代码
过程如下所示:
从上面可以看出,引发段错误的代码在seg_error.c的第16行处,即memset(a, 0, sizeof(double) * m * n);
接下来便是去找引发段错误的原因了,这个就需要根据具体的代码来判断了。经过一番debug,最终发现了问题所在:
- 传递给mallc的参数是
m * n * sizeof(double)
,编译器把该语句编译成了先把m和n相乘,得到的结果再和sizeof(double)相乘,m和n都是int类型,由于输入时m和n较大,两者相乘直接溢出,溢出的结果再和sizeof(double)相乘便得到了一个错误的值。- 传递给memset的参数是
sizeof(double) * m * n
,编译器把该语句编译成了先把sizeof(double)和m相乘,得到的结果再和n相乘,由于sizeof(double)是size_t类型(在我的电脑里,size_t就是unsigned long类型而且该类型为64位),因此会先把m强制转化为size_t类型后再和sizeof(double)相乘,此时不会发生溢出,得到的结果再和n相乘,同理,会先把n强制转化为size_t类型,因此最终的结果是正确的值- 由于我输入的m和n均为
131072
( 2 17 2^{17} 217),因此m * n * sizeof(double)
得到的值实际上是0,但是sizeof(double) * m * n
的值为137438953472,因此memset会引发段错误