由 MATLAB MEX 函数中的“优化输出”值引起的 GCC 段错误

MrA*_*man 5 c matlab gcc mex segmentation-fault

我正在尝试编写一个相当简单的递归洪水填充算法(作为 MATLAB mex 函数运行),但是在打开 GCC 中的优化标志时遇到了问题(如果重要的话,v 7.5.0)。代码在没有打开任何优化标志的情况下工作正常,但是当我使用 -O2 或 -O3 标志时会出现段错误。我已经将罪魁祸首缩小到一个被 GCC 优化的索引变量——如果我将它指定为一个易失性变量,即使在更高的优化级别上也不会发生段错误。我假设我必须使用未定义的行为,但我看不出这可能发生在哪里。

有问题的代码片段:

#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);
}
Run Code Online (Sandbox Code Playgroud)

最后的样板文件是允许编译和从 MATLAB 传递数据(这是针对 MATLAB MEX 函数)。GDB 和 Valgrind 都说段错误发生在fill函数内,但没有指定确切的位置——我必须从 MATLAB 调用它,所以输出有点混乱。Valgrind 指出,段错误的原因是“地址 0x27E33F70 处映射区域的权限错误”。

据我所知,代码不应该出现段错误——我总是在访问数组之前进行边界检查image,并且数组是用 size 创建的x*y==n。最让我困惑的是,如果我指定ivolatile,代码可以正常工作,这表明 GCC 可能会优化我的一项边界检查。我意识到我可以保持原样,但我担心这可能表明一个更大的问题可能会在以后再次困扰我。

作为附录,我尝试剥离 MATLAB 代码并在 MATLAB 之外运行它,但这会导致问题不再发生。我不知道添加的代码是否使GCC对其进行了不同的编译。但这不是解决方案,因为它需要从 MATLAB 内部运行。

Cri*_*ngo 2

根据我的经验,与通过调试器运行程序相比,在打开 AddressSanitizer 的情况下进行编译是查找问题提示的更好方法。只需添加-fsanitize=address到 GCC 命令行即可。启动 MATLAB 时,您可能需要预加载 ASan 库:LD_PRELOAD=/path/to/asan/runtime/lib matlab

我的直觉是,由于似乎没有办法索引越界,所以问题是堆栈溢出。OP 的评论证实了这一点,尽管出现这种情况的原因很难理解。添加volatile参数或printf语句极大地影响优化器如何更改生成的程序集。

在所有其他解释都用尽之前,永远不要责怪编译器。但这种行为肯定有可能是由编译器中的错误引起的。如果代码有问题,我不会看到它。

另一方面,在非递归函数中使用您自己的堆栈编写洪水填充算法通常要好得多。我发现它使代码更高效且更易于阅读。