【问题标题】:2D array seg fault in CC中的二维数组段错误
【发布时间】:2020-06-15 07:42:11
【问题描述】:

我正在尝试取消引用函数 islandPerimeter 中的二维数组。
但我不明白为什么我会为此遇到段错误。
有人能指出我到底做错了什么吗?

更新: 所以这是我试图解决的 leetcode 问题的一部分。我现在明白它不是二维数组而是指针。我仍然对 int** 感到困惑。有人可以解释一下吗?

#include <stdio.h>

int islandPerimeter(int** grid, int gridSize, int gridColSize)
{
    int perimeter=0,points=4,i=0;

    for(int row=0;row<gridSize;++row)
    {
        for(int col=0;col<gridColSize;++col)
        {
            printf("%d ",grid[row][col]);
        }
    }
    return perimeter;
}


int main()
{
    int arr[4][5] = {{8,1,0,0,0},
                     {1,1,1,0,0},
                     {0,1,0,0,0},
                     {1,1,0,0,0}};

    islandPerimeter(arr,4,5);

    return 0;
}

【问题讨论】:

  • int[][] 与int ** 不同
  • 编译器无法知道您传入的 int** 的尺寸。我不知道它为什么会编译或出现段错误,但我可以看到如果没有这些信息它就无法正常工作. Chris 的解决方案是正确的。
  • @donjuedo 取决于编译器及其标志,它只会针对不兼容的类型发出警告。
  • int** grid --> int (*grid)[5]
  • int** grid 不是二维数组。 grid 是一个指针。

标签: c arrays multidimensional-array segmentation-fault


【解决方案1】:

假设您想保留函数原样,而是更改二维数组在 main(或任何其他调用函数)中的初始化方式。如果数组数据是由用户输入或在运行时从文件加载的,这也是您必须做的,因此了解以下信息很有用:

int main(void) {
    const int ROWS = 4;  //these don't have to be const;
    const int COLS = 5;
    const int data[20] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20};

    int** pointer_arr = malloc(ROWS * sizeof(int*)); //allocate space for each ptr
    //error check
    if (pointer_arr == NULL) {
        printf("Unsuccessful ptr-ptrarray allocation attempt\n");
        exit(0);
    }

    for (int i = 0; i < ROWS; ++i) {
        pointer_arr[i] = malloc(COLS * sizeof(int));  //allocate space for each int
        //error check with alternative indexing syntax (same as pointer_arr[i])
        if (*(pointer_arr + i) == NULL) {
            printf("Unsuccessful ptr-intarray allocation attempt\n");
            exit(0);
        }
    }
    //load each allocated int address space with an int from data:
    for (int i = 0; i < ROWS ; ++i) {
        for (int j = 0; j < COLS; ++j) {
            pointer_arr[i][j] = data[ROWS * i + j];
        }
    }
    //Now you can call your unaltered function and it will perform as expected: 
    islandperimeter(pointer_arr, ROWS, COLS);
    return 0;
}

在正常情况下(程序不会立即终止)请注意,您必须手动释放所有分配的内存,否则会发生内存泄漏。

【讨论】:

    【解决方案2】:

    指向数组的指针

    数组是 C 中的一种不同类型。它是给定类型元素的顺序集合。在 C 中,二维数组实际上是一维数组的数组。在您的情况下,您有一个 int [5] 的数组 [4](例如,int 的 4 - 5 元素数组通常称为 int 的二维数组)

    新程序员通常会感到困惑的地方是在访问时如何处理数组。访问数组时,会将其转换为指向第一个元素的指针。 C11 Standard - 6.3.2.1 Other Operands - Lvalues, arrays, and function designators(p3)(注意4个异常)

    在一维数组的情况下,很简单,数组被转换为指向数组第一个元素的指针(指针就是int*)。在二维数组的情况下,同样适用,数组被转换为指向第一个元素的指针——但第一个元素是一个 5-int 的一维数组。 (指针是int [5] 的指向数组的指针,正式为int (*)[5])

    您可以将二维数组(在您的情况下)作为int grid[4][5]、int grid[][5] 的参数传递,或者将数组转换为指向第一个元素int (*grid)[5] 的指针。关键是您必须始终为您的数组提供最终维度中的元素数量(对于此处不相关的情况允许额外的'*')5(或元素数量)必须是已知的integer constant在编译时,除非使用 可变长度数组 (VLA),这是单独讨论的主题。

    在访问数组时将其转换为指向其第一个元素的指针的相同规则适用于数组中的每个维度,无论是 2D 数组还是 6D 数组。 C11 Standard - 6.5.2.1 Array subscripting(p3)

    此外,了解指向数组的指针(例如int (*grid)[5])和指针数组(例如int *grid[5])之间的区别。由于C Operator Precedence 需要括号,在这种情况下[..] 的优先级高于'*',因此要求*grid(在int *grid[5] 中)被评估为指针 (而不是一个数组 grid[5])你把它括起来是括号(*grid)。 因此导致 pointer-to-array 的 int [5], (int (*grid)[5]) 而不是 array-of-pointers 到 int(其中 5 个) int *grid[5]。

    指向指针的指针

    与 pointer-to-pointer(例如 int **,通常称为双指针)进行对比。您有两个** 表示的两个间接级别。指针本身是一个单指针——指向什么? (另一个指针,不是一个数组)。您通常会通过首先分配一块内存来保存一定数量的指针来使用双指针,例如当您动态分配未知数量的已分配对象时。这可以是int 的未知数量的列的未知数量的行,或者它可以是未知数量的字符串,或未知数量的结构等。关键是你的第一级间接指向的内存包含指针。

    然后,对于每个可用的 指针,您可以分配一个块(例如,在您的情况下保存 5 int,然后将该内存块的起始地址分配给您的第一个可用指针) .您继续为您的列(或字符串或结构)分配并按顺序将起始地址分配给每个可用指针。完成后,您可以使用与二维数组相同的索引来访问已分配集合中的各个元素。这样的集合和数组的二维数组之间的区别——每个指针指向的内存在内存中不必是连续的。

    区分他们

    知道使用哪个的关键是问“我的指针指向什么?”它指向一个指针吗?或者,它是否指向一个数组?如果它指向另一个指针,那么你有一个 pointer-to-pointer。如果指向的东西是一个数组,那么你有一个 pointer-to-array。有了它,你就知道你需要什么作为参数了。

    为什么 SegFault 带有 int**

    类型控制指针运算。回想一下,int** 是一个 pointer-to-pointer,那么指针有多大呢? (sizeof (a_pointer) - x86_64 上通常为 8 字节,x86 上通常为 4 字节)。所以grid[1][0] 距离grid[0][0] 只有一个指针(8 字节)。 指向数组的指针呢?第一个索引中的每个增量都是一个sizeof (int[5]),除了第一个。所以在 4x5 数组的情况下,grid[1][0] 是 5 * sizeof(int)(20 字节),与 grid[0][0] 不同。

    因此,当尝试使用int** 访问您的数组数组时,以grid[1][3](或32 位框上的grid[1][4])开头,您正在读取第一行值的末尾。 (您有 8 个字节的偏移量(一个指针 8 个字节 - 跳过 2 个整数),将您放在第一行的第三个整数之前,然后再偏移 3 个整数,将您放在过去的 grid[0][5]第一行中的最后一个值grid[0][4]。(每行增量都会复合)结果未定义,任何事情都可能发生。

    当您传递适当的指向数组的指针时,行索引的每个增量偏移 20 个字节,将您置于下一个 1D 值数组的开头,以便遍历每个列保持在该一维数组的范围内。

    仔细考虑,如果您还有其他问题,请告诉我,我很乐意为您提供进一步帮助。

    【讨论】:

      【解决方案3】:

      int** grid 是一个pointer to pointer to int。它缺少数组宽度的信息。

      从 C99 或 C11 开始,带有可选的可变长度数组:

      // int islandPerimeter(int** grid, int gridSize, int gridColSize)
      int islandPerimeter(int gridSize, int gridColSize, int grid[gridSize][gridColSize]) {
        int perimeter=0;
      
        for(int row=0;row<gridSize;++row) {
          for(int col=0;col<gridColSize;++col) {
            printf("%d ",grid[row][col]);
          }
        }
        return perimeter;
      }
      

      调用

      islandPerimeter(4, 5, arr);
      

      【讨论】:

        【解决方案4】:

        试试这个

        int islandPerimeter(int* grid, int gridSize, int gridColSize) {
            int perimeter = 0, points = 4, i = 0;
        
            for(int row=0; row < gridSize; ++row) {
                for(int col = 0; col < gridColSize; ++col) {
                  printf("%d ",grid[row*gridColSize + col]);
                }
            }
            return perimeter;
        }
        

        您必须将调用更改为

        islandPerimeter((int *)grid, 4, 5);
        

        【讨论】:

        • 请注意,实现者还必须更改arr的声明及其初始化
        • @dvhh 声明/初始化是否必须更改?根据我的理解,数组在内存中是连续布局的,因此按照我的答案对其进行索引应该可以工作。
        • 考虑避免演员表。 islandPerimeter(grid[0], 4, 5);
        猜你喜欢
        • 2016-05-07
        • 1970-01-01
        • 2015-11-11
        • 2019-08-03
        • 1970-01-01
        • 2010-09-26
        • 2012-09-09
        • 1970-01-01
        • 2019-01-28
        相关资源
        最近更新 更多