由 MATLAB MEX 函数中的 "optimised out" 值引起的 GCC 段错误
GCC segfault caused by "optimised out" value in MATLAB MEX function
我正在尝试编写一个相当简单的递归填充算法(运行 作为 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 都说段错误发生在 fill
函数中,但没有具体说明位置——我必须从 MATLAB 调用它,所以输出有点混乱。 Valgrind 声明段错误的原因是 "Bad permissions for mapped region at address 0x27E33F70"。
据我所知,代码 不应该 段错误——我总是在访问数组 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
.
我的直觉是,由于似乎没有索引越界的方法,所以问题是堆栈溢出。 证实了这一点,尽管出现这种情况的原因很难理解。添加 volatile
参数或 printf
语句会极大地影响优化器如何更改生成的程序集。
在穷尽所有其他解释之前,永远不要责怪编译器。但这种行为肯定有一种可能是由编译器中的错误引起的。如果代码有问题,我看不到它。
另一方面,在非递归函数中使用您自己的堆栈编写泛洪填充算法通常会好得多。我发现它使代码更高效也更易于阅读。
我正在尝试编写一个相当简单的递归填充算法(运行 作为 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 都说段错误发生在 fill
函数中,但没有具体说明位置——我必须从 MATLAB 调用它,所以输出有点混乱。 Valgrind 声明段错误的原因是 "Bad permissions for mapped region at address 0x27E33F70"。
据我所知,代码 不应该 段错误——我总是在访问数组 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
.
我的直觉是,由于似乎没有索引越界的方法,所以问题是堆栈溢出。 volatile
参数或 printf
语句会极大地影响优化器如何更改生成的程序集。
在穷尽所有其他解释之前,永远不要责怪编译器。但这种行为肯定有一种可能是由编译器中的错误引起的。如果代码有问题,我看不到它。
另一方面,在非递归函数中使用您自己的堆栈编写泛洪填充算法通常会好得多。我发现它使代码更高效也更易于阅读。