Ошибка сегмента 2D массива в C - PullRequest
0 голосов
/ 03 марта 2020

Я пытаюсь отменить ссылку на 2D-массив внутри функции islandPerimeter.
Но я не могу понять, почему я получаю segfault за это.
Может кто-то указать, что именно я делаю неправильно?

обновление: Так что это была часть проблемы из кода leetcode, который я пытался решить. Теперь я понимаю, что это не 2D-массив, а указатель. Я все еще смущен по поводу **. кто-нибудь может это объяснить?

#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;
}

Ответы [ 4 ]

1 голос
/ 03 марта 2020

int** grid - это указатель на указатель на 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);
1 голос
/ 03 марта 2020

Указатель на массив

Массив является отдельным типом в C. Это последовательные коллекции элементов заданного типа. В C двумерный массив фактически является массивом одномерных массивов. В вашем случае у вас есть массив [4] int [5] (например, массивы из 4-5 элементов int, обычно называемые двумерным массивом int)

. как массив обрабатывается при доступе. При обращении к массиву он преобразуется в указатель на первый элемент. Стандарт C11 - 6.3.2.1 Другие операнды - L-значения, массивы и обозначения функций (p3) (обратите внимание на 4 исключения)

В случае одномерного массива это просто массив преобразуется в указатель на первый элемент массива (указатель просто int*). В случае двумерного массива то же самое верно, массив преобразуется в указатель на первый элемент - но этот первый элемент является одномерным массивом с 5-целым числом. (указатель является указатель на массив из int [5], формально int (*)[5])

Вы можете передать двумерный массив (в вашем случае) в качестве параметра либо int grid[4][5], int grid[][5] или для отражения того, что массив преобразован в указатель на первый элемент, int (*grid)[5]. Ключ заключается в том, что вы всегда должны указывать количество элементов в конечном измерении для вашего массива (с дополнительными '*', разрешенными для обстоятельств, которые здесь не имеют значения) 5 (или количество элементов) должно быть integer constant, которое известно во время компиляции, если не используется массив переменной длины (VLA), которые являются топи c для отдельного обсуждения.

То же правило, что при доступе массив преобразуется в указатель на его первый элемент применяется к каждому измерению в вашем массиве, будь то 2D-массив или 6D-массив. C11 Standard - 6.5.2.1 Подписка на массив (p3)

Кроме того, необходимо знать разницу между указателем на массив (например, int (*grid)[5]) и массив указателей (например, int *grid[5]). Скобки требуются из-за C Приоритет оператора , [..] имеет более высокий приоритет, чем '*' в этом случае, поэтому требуется, чтобы *gridint *grid[5]) оценивалось как указатель (вместо массива grid[5]), заключенный в скобки (*grid). Таким образом, получается указатель на массив из int [5], (int (*grid)[5]) вместо массив-указателей до int (5 из них) с int *grid[5].

Указатель на указатель

Сравните это с указатель на указатель (например, int **, обычно называемый двойным -указатель). У вас есть два уровня косвенности, представленные двумя **. Сам указатель является одиночным указателем - к чему? (другой указатель , а не массив ). Как правило, вы будете использовать двойной указатель, сначала выделяя блок памяти для хранения некоторого числа указателей , например, когда вы динамически выделяете неизвестное количество выделенных объектов. Это может быть неизвестное количество строк с неизвестным числом столбцов int, или это может быть неизвестное количество строк или неизвестное количество структур, и т. Д. c .. Ключ - ваш первый уровень точек косвенности в память, содержащую указатели .

Затем для каждого из доступных указателей вы можете выделить блок (например, в вашем случае удерживать 5 int, а затем назначить начальный адрес этого блока памяти для вашего первого доступного указателя). Вы продолжаете выделять для своих столбцов (или строк или структур) и назначаете начальный адрес каждому из ваших доступных указателей в последовательности. Когда вы закончите, вы можете получить доступ к отдельным элементам в выделенной коллекции, используя ту же индексацию, что и для двумерного массива. Разница между такой коллекцией и двумерным массивом массивов заключается в том, что память, на которую указывает каждый указатель, не обязательно должна быть последовательной в памяти.

Разъединяя их

Ключ к знанию того, что использовать, - спросить " На что указывает мой указатель? " Указывает ли он на указатель? Или это указывает на массив? Если он указывает на другой указатель, то у вас есть указатель на указатель . Если указанная вещь является массивом, то у вас есть указатель на массив . При этом вы знаете, что вам нужно в качестве параметра.

Почему SegFault с int **

Тип управляет арифметикой указателя c. Напомним выше, int** это указатель на указатель , так насколько велик указатель? (sizeof (a_pointer) - обычно 8 байтов в x86_64 или 4 байта в x86). Таким образом, grid[1][0] находится всего в одном указателе (8 байт) от grid[0][0]. Что насчет указателя на массив ? Каждый шаг в первом индексе на sizeof (int[5]) отличается от первого. Таким образом, в случае массива 4x5 grid[1][0] равно 5 * sizeof(int) (20 байтов), кроме grid[0][0].

Таким образом, пытаясь получить доступ к вашему массиву массивов, используя int**, начиная с grid[1][3] (или grid[1][4] в 32-битном поле), вы читаете один за концом 1-й строки ценностей. (у вас есть смещение на 8 байтов (один указатель на 8 байтов - пропускаем 2-целое), помещая вас непосредственно перед третьим целым числом в 1-й строке, затем смещая еще на 3 целых, помещая вас в то, что будет grid[0][5] на один последнее значение в 1-й строке grid[0][4]. (составляется с каждым шагом строки) Результат не определен, и может произойти все что угодно.

Когда вы передаете соответствующий указатель на массив каждое смещение индекса строки смещается на 20 байтов, помещая вас в начало следующего одномерного массива значений, поэтому итерации по каждому столбцу остаются в границах этого одномерного массива.

Продумайте это, и если у вас есть дополнительные вопросы, просто дайте мне знать, и я буду рад помочь вам.

0 голосов
/ 03 марта 2020

Допустим, вы хотите оставить свою функцию как есть и вместо этого изменить способ инициализации 2D-массива в 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;
}

В нормальных условиях (когда программа не ' немедленно прекратить работу) обратите внимание, что тогда вам придется вручную освободить всю выделенную память или получить утечку памяти.

0 голосов
/ 03 марта 2020

Попробуйте это

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);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...