C - 分段错误读取结构和 Dynamic2D 数组成员

C - Segmentation Fault Reading Struct and Dynamic2D Array Member

我刚开始学习 C,我想尝试创建一个使用指针、结构和数组的测试程序,因为我仍然很难理解它们。我创建了这个测试文件,它是我正在处理的一个更大项目的精简版本。测试文件有一个结构,其中有一个动态二维数组作为结构的成员:

typedef struct {
  int ** array;
  int rows, cols;
} Smaller;

但是,运行测试文件后终端returns出现如下错误:

zsh: segmentation fault ./a.out

我研究了这个错误的含义,

" Segmentation fault is a specific kind of error caused by accessing memory that “does not belong to you.” " (Link)

但我仍然对如何解决这个问题感到困惑。我很确定我为每一行和每一列分配了正确数量的内存。更令人困惑的是,终端并没有指出错误在哪一行。如果能就此问题提供任何帮助,我将不胜感激。

完整代码如下:

#include <stdio.h>
#include <stdlib.h>

typedef struct {
  int ** array;
  int rows, cols;
} Smaller;


void printArray (Smaller * s);

int main () {
  int x, i, j;

  Smaller * sand;

  // allocate mem for number of rows
  sand->array = malloc (3 * sizeof(int *));

  //allocate mem for number of columns
  sand->array = malloc(4 * sizeof(int));
  sand->array = malloc(4 * sizeof(int));
  sand->array = malloc(4 * sizeof(int));

  // adding a constant value to the 2D array
  for (i = 0; i < 3; i ++) {
    for (j = 0; j < 4; j ++) {
      sand->array[i][j] = 6;
    }
  }

  printArray(sand);

  return 0;
}

void printArray (Smaller * sand) {
  printf("Welcome to the printArray function! \n");

  int i, j;

  for (i = 0; i < 3; i ++)
    for(j = 0; j < 4; j ++)
      printf("array[%d][%d] = %d \n", i, j, sand->array[i][j]);

}

问题是,正如@tromgy 指出的那样,您正在用列数组覆盖基数 sand->array,而不是将它们分配给它。正确的代码如下所示:

#include <stdlib.h>
#define NUM_ROWS 3
#define NUM_COLS 4

typedef struct {
    int ** array;
    int rows;
    int cols;
} Smaller;

void print_array(Smaller * s);

int main(void) {
    Smaller * sand = malloc(sizeof(Smaller));
    if (!sand) return -1; /* allocation failed, abort */
    sand->rows = NUM_ROWS;
    sand->array = malloc(sizeof(int*[NUM_ROWS]));
    if (!sand->array) { /* allocation failed, abort */
        free(sand); /* free sand first, though */
        return -1;
    }
    for (size_t i = 0; i < NUM_ROWS; ++i) {
        sand->array[i] = malloc(sizeof(int[NUM_COLS]));
        if (!sand->array[i]) {
            /* free the previous rows */
            for (size_t j = 0; j < i; ++j) free(sand->array[j]);
            free(sand->array);
            free(sand);
            return -1;
        }
    }
    /* add a constant value to the array */
    for (size_t i = 0; i < NUM_ROWS; ++i) {
        for (size_t j = 0; j < NUM_COLS; j ++) {
            sand->array[i][j] = 6;
        }
    }
    print_array(sand);
    /* Ok, now free everything */
    for (size_t i = 0; i < NUM_COLS; ++i) {
        free(sand->array[i]);
    }
    free(sand->array);
    free(sand);
    /* NOW we may exit */
    return 0;
}

如您所见,分配这样的结构需要大量工作,而且您必须释放分配的任何内容,因此最好将其提取到函数中,例如 Smaller * smaller_init(size_t nrows, size_t ncols)void smaller_destroy(Smaller * s) 封装所有工作。

我将在下面留下一个示例,以便您可以将其与您最初编写的方式进行比较...

关于您的代码:

  • for 命令中声明循环变量
  • 可能是Smaller不需要是指针
  • 将尺寸保持为变量。更灵活
  • 您没有在 struct 中设置 rowscols 的值。并且在 main() 中不要像您那样使用固定值 3 和 4
  • 您应该将所有单元格设置为不同的值,而不是相同的。当您看到可逆值时,您会感觉更安全,例如示例中的 100*row + column ... 这样您就可以查看循环是否正常以及是否正在打印所有元素。查看 printArray():
  • 的输出
      0    1    2    3
    100  101  102  103
    200  201  202  203

每一行都以行号开头,因此您可以在继续之前测试几次。

  • 让你的程序自我测试。例如在 printArray() 中显示这样的尺寸:
    printArray[3,4]

      0    1    2    3
    100  101  102  103
    200  201  202  203

查看示例的输出

  • 总是按照分配的保留顺序编写释放内存的代码,也许在一个单独的函数中 returns NULL 以使调用代码中的指针无效,就像这样
Smaller* freeArray(Smaller* A)
{
    printf("\nfreeArray()\n");
    for (int i = 0; i < A->rows; i++)
    {
        free(A->array[i]);  // delete lines
        printf("row %d free()\n", i);
    }
    free(A->array);  // delete cols
    printf("pointer to rows free()\n");
    free(A);  // delete struct
    printf("struct free()\n");
    return NULL;
}

这样你就知道指针 sand 不会指向已经 free()d 的区域。使用这样的指针会使您的程序崩溃,因此最好编写

    sand = freeArray(sand);

示例代码的输出

printArray[3,4]

  0    1    2    3
100  101  102  103
200  201  202  203

freeArray()
row 0 free()
row 1 free()
row 2 free()
pointer to rows free()
struct free()

示例代码

#include <stdio.h>
#include <stdlib.h>

typedef struct
{
    int** array;
    int   rows, cols;

} Smaller;

void     fillArray(Smaller*);
Smaller* freeArray(Smaller*);
Smaller* makeArray(size_t, size_t);
void     printArray(Smaller*);

int main(void)
{
    int y = 3; 
    int x = 4;
    // sand points to a Smaller
    Smaller* sand = makeArray(y, x);
    // adding known unique values to cells is easier
    fillArray(sand);
    printArray(sand); // show values
    sand = freeArray(sand); // delete all
    return 0;
}

void fillArray(Smaller* A)
{
    for (int i = 0; i < A->rows; i++)
        for (int j = 0; j < A->cols; j++)
            A->array[i][j] = 100 * i + j;
}

Smaller* freeArray(Smaller* A)
{
    printf("\nfreeArray()\n");
    for (int i = 0; i < A->rows; i++)
    {
        free(A->array[i]);  // delete lines
        printf("row %d free()\n", i);
    }
    free(A->array);  // delete cols
    printf("pointer to rows free()\n");
    free(A);  // delete struct
    printf("struct free()\n");
    return NULL;
}

Smaller* makeArray(size_t y, size_t x)
{
    // sand points to a Smaller
    Smaller* sand = (Smaller*)malloc(sizeof(Smaller));
    sand->rows    = y;
    sand->cols    = x;

    // allocate mem for number of rows, that is 'y'
    sand->array = malloc(y * sizeof(int*));

    // allocate mem for each of the 'x' columns
    for (size_t i = 0; i < y; i++)
        sand->array[i] = malloc(x * sizeof(int));

    return sand;
};

void printArray(Smaller* sand)
{
    printf("printArray[%d,%d]\n\n", sand->rows, sand->cols);
    for (int i = 0; i < sand->rows; i++)
    {
        for (int j = 0; j < sand->cols; j++)
            printf("%3d  ", sand->array[i][j]);
        printf("\n");
    }
}

关于代码

请大家不要费心指点我不要投malloc()的结果。这是由决定。这个常见的建议是对 90 年代 C-faq 的回忆,现在我们知道隐式转换可能不太好。事实上,隐式的事情可能会花费你很多时间:如果你 malloc() 程序中的一系列不同的结构并省略类型(如果其中一些是相反的)请记住,使用所有类型转换会有所帮助您避免了这种代价高昂的错误...