我正在尝试编写一个相当简单的递归泛洪算法(以MATLAB mex函数的形式运行),但是在GCC中打开优化标志(如果重要,请参见v 7.5.0)时遇到了问题。该代码可以在没有打开任何优化标志的情况下正常运行,但是当我使用-O2或-O3标志时会出现段错误。我将罪魁祸首缩小到了由GCC优化的索引变量-如果我将其指定为volatile变量,则即使在更高的优化级别上也不会发生段错误。我认为我必须使用未定义的行为,但是我看不到可能在哪里发生。
令人反感的代码段:
#include "mex.h"
#include <string.h>
// Removing this causes the program to segfault -----v
void fill(double *image, signed int x, signed int n, volatile signed int i, double k)
{
image[i] = k;
if ((i-1) >= 0 && ((i-1) % x) < (i % x) && image[i-1]==1)
fill(image,x,n,i-1,k);
if ((i-x) >= 0 && image[i-x]==1)
fill(image,x,n,i-x,k);
if ((i+1) < n && ((i+1) % x) > (i % x) && image[i+1]==1)
fill(image,x,n,i+1,k);
if ((i+x) < n && image[i+x]==1)
fill(image,x,n,i+x,k);
}
// image is a 1D array holding a 2D image of size <x,y>
void flood(double *image, signed int x, signed int y)
{
signed int n = x*y;
signed int i = 0;
double k = 2;
while (i < n)
{
while(i<n && image[i] != 1) ++i;
if(i>=n) return;
fill(image,y,n,i,k);
++k;
++i;
}
}
void mexFunction(int nlhs, mxArray *plhs[],int nrhs, const mxArray *prhs[])
{
int n;
double *image;
size_t x, y;
if(nrhs!=1)
{
mexErrMsgIdAndTxt("floodfill:nrhs","One input required.");
}
if(nlhs!=1)
{
mexErrMsgIdAndTxt("floodfill:nlhs","One output required.");
}
if( !mxIsDouble(prhs[0]) ||
mxIsComplex(prhs[0]))
{
mexErrMsgIdAndTxt("floodfill:doubleMatrix","Input 1 must be real double matrix.");
}
x = mxGetM(prhs[0]);
y = mxGetN(prhs[0]);
plhs[0] = mxCreateDoubleMatrix( (mwSize)x, (mwSize)y, mxREAL);
image = mxGetPr(plhs[0]);
memcpy(image,mxGetPr(prhs[0]),sizeof(double)*x*y);
flood(image,y,x);
}
最后的样板是允许从MATLAB编译和传递数据(这是针对MATLAB MEX函数的)。 GDB和Valgrind都说segfault发生在fill
函数中,但是没有指定确切的位置-我必须从MATLAB调用它,因此输出有些混乱。 Valgrind指出发生段错误的原因是“地址0x27E33F70的映射区域的权限错误”。
据我所知,代码不应该 segfault-在访问数组image
之前,我总是进行边界检查,并且创建的数组的大小为x*y==n
。最让我困惑的是,如果我将i
指定为volatile
,则代码可以正常工作,这表明GCC可能在优化我的边界检查之一。我意识到我可以保持原样,但我担心这可能表明存在更大的问题,以后可能会再次引起我的注意。
作为附录,我尝试剥离MATLAB代码并在MATLAB之外运行它,但这导致不再发生此问题。我不知道添加的代码是否会使GCC进行不同的编译。但是,这不是解决方案,因为它需要在MATLAB内部运行。
根据我的经验,与通过调试器运行程序相比,打开AddressSanitizer进行编译是查找问题提示的更好的方法。只需将-fsanitize=address
添加到GCC命令行即可。启动MATLAB时,您可能需要预加载ASan库:LD_PRELOAD=/path/to/asan/runtime/lib matlab
。
我的直觉,因为似乎没有一种方法可以超出范围,所以问题是堆栈溢出。尽管很难理解,但comment by OP确认了这一点。添加volatile
参数或printf
语句会极大地影响优化器如何更改生成的程序集。
除非用尽所有其他解释,否则永远不要怪编译器。但是,这种行为肯定是由编译器中的错误引起的。如果代码有问题,我看不到。
另一方面,在非递归函数中使用自己的堆栈编写泛洪填充算法通常要好得多。我发现它使代码更高效且更易于阅读。