首页 > 解决方案 > C - 函数调用本身会导致递归调用中的段错误

问题描述

我正在与一个我无法理解的段错误作斗争:我有一个递归函数,它在表示像素的数组上展开:从索引开始,它围绕索引展开,通过左右上下调用相同的函数来创建像素组(又名索引-1,索引+1 ...)。出于调试目的,我在函数的第一行有一个 printf 调用,并且在 4 个递归调用中的每一个之前都有一个调用。我没有得到的是,在递归调用本身的递归过程中,我最终遇到了段错误(我得到了调用之前的打印,但不是函数开始时的打印)。

void explore(Pixel * array, int j, Tache * cur_pound, int * it, Pixel previous){
    printf("%d\n", j); // I DONT GET THIS PRINT AT LAST RECURSIVE CALL
    // out of bounds
    if(j > sizeX * sizeY)
        return;

    // allready explored index
    if(array[j].explored == 1){
        return;
    }

   // to big of a color difference between this pixel and the reference one
   if(abs((int)array[j].r - previous.r) > SEUIL || abs((int)array[j].g - previous.g) > SEUIL || abs((int)array[j].b - previous.b) > SEUIL){
      return;
   }

   array[j].explored = 1;
   cur_pound->limits[* it] = j;
   (* it)++;

  // recursion
  if(j +1 < sizeX * sizeY && array[j+1].explored != 1){
        printf("before SF\n); // I GET THIS PRINTF
        explore(array, j + 1, cur_pound, it, previous);
  }
  // 3 other recursive calls removed for simplicity here
}

关于我的数据结构: aTache *struct包含 3 GLubytes 和limits, aint *代表属于该组的每个像素索引。APixel包含 3 GLubytes 和 achar表示该像素是否已被函数访问。作为第一个参数的array函数是一个Pixel代表我的图像的数组。 it是一个int表示组中的索引,以便我的函数知道它应该在数组的哪个位置添加新索引。 Limits在此函数外部初始化-1并分配malloc(size * sizeof(int))其中size图像的宽度乘以它的高度。

这就是初始调用的完成方式:

void taches_de_couleur(Image *i){
    int j, k, y, size, it;
    GLubyte * im;
    Pixel * array;

    sizeX = i->sizeX;
    sizeY = i->sizeY;  
    k = 0;
    size = sizeX * sizeY;
    array = malloc(size * sizeof(Pixel));
    im = i->data;

   /* build the array from image data */
   for(j = 0; j < 3 * size; j+= 3){
       array[k].explored = 0;
       array[k].r = i->data[j];
       array[k].g = i->data[j + 1];
       array[k].b = i->data[j + 2];
       k++;
    }

    Tache * new_pound;
    new_pound = malloc(sizeof(Tache));
    new_pound->limits = malloc(size * sizeof(int));
    int x= 0;
    while(x < size){
        new_pound->limits[x] = -1;
        x++;
    }
    it = 0;
    explore(array, 0, new_pound, &it, array[0]);
}

请注意,该程序在处理小图像时不会产生任何 SF(我能做的最大的是 512x384px)。这件事让我头疼了一个星期,无法弄清楚是什么导致了这个段错误,这就是为什么我问你们是否可以在这里看到任何明显的东西。如果需要,我可以添加调用 explore 的第二个函数,但这部分似乎很好。

编辑:这是当我用太大的图像运行它时 gdb 给我的输出:

Thread 1 "palette" received signal SIGSEGV, Segmentation fault.
0x00007ffff7b730be in __GI___libc_write (fd=1, buf=0x555555592770, 
nbytes=7)
at ../sysdeps/unix/sysv/linux/write.c:26
26  ../sysdeps/unix/sysv/linux/write.c: No such file or directory.

编辑:由于我未能提供足够的资源,请参阅https://github.com/BruhP8/TachesDeCouleur了解完整项目提前致谢

标签: carraysrecursionsegmentation-fault

解决方案


我没有得到的是,在递归调用本身的递归过程中,我最终遇到了段错误(我得到了调用之前的打印,但不是函数开始时的打印)。

这几乎肯定是堆栈耗尽的迹象。

在调试器下运行您的程序,并检查导致段错误的指令。很可能,它将是堆栈操作指令(CALL, PUSH)之一,或堆栈减量之后的堆栈取消引用指令。您还可以查看$SP寄存器的值,并将其与堆栈段的边界进行比较(/proc/$pid/maps如果您使用的是 Linux)。

您显示的代码似乎没有分配任何堆栈,因此问题可能出在您省略的代码中。

请注意,该程序在处理小图像时不会产生任何 SF

这是另一个迹象:您可能正在堆栈上分配一个新图像,并且图像越大,您可以实现的递归级别越少。

PS 在 Linux 上,默认堆栈大小通常为 8MiB。试试ulimit -s unlimited——如果这能让程序更深入地重现,那将是我的猜测是正确的肯定信号。但不要ulimit -s unlimited用作修复(不是)。

更新:

使用完整的源代码,我能够构建palette程序。每个递归调用explore只需要 48 个字节的堆栈(不多)。

但是使用默认的 8MiB 堆栈,这将总递归限制在(8 << 20) / 48 == 174762深度级别。

TL;DR:如果您的递归过程需要每像素一级递归,那么您将无法处理大图像。您必须将过程重写为迭代。


推荐阅读