В C почему мне НЕ нужно указывать размер 2D-массива при переходе в функцию, когда 2D-массив создается с помощью mallo c? - PullRequest
2 голосов
/ 22 марта 2020

Я довольно новичок с C и просто запутался в том, что на самом деле происходит, когда я передаю 2D-массивы, выделенные в HEAP памяти, в функцию. Я написал код, который имеет три функции, A, B, C, который демонстрирует мой вопрос.

По сути, когда я создаю двумерный массив в пространстве стека в функции-A, я могу передать указатель этого двумерного массива в функцию-B, для которой требуется параметр (int size, int (*arr)[size]), и он работает нормально. Насколько я понимаю, переменная 'int size' требуется, чтобы указатель arr теперь мог указывать, сколько места он должен перескакивать при каждом увеличении

Однако, когда я создаю 2d-массив в пространстве HEAP в функции-A, передавая его функции -B, похоже, теряет местоположение данных (см. Код). Однако, если я передам этот массив пространства HEAP 2d функции- C, которая имеет параметр (int **arr), он будет работать нормально.

Было бы здорово, если бы кто-нибудь попытался объяснить, почему мне не нужно указывать размер при передаче массива 2d пространства HEAP в функцию - C. Кроме того, когда я передаю 2d массив, созданный в STACK пространстве, в функцию- C, происходит сбой, почему это так?

Вот пример кода, демонстрирующий мой вопрос ( Вывод это ):

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

void function_A(int num)
{
    // allocating HEAP space for 2D array
    int **arrHEAP = (int **)malloc(2*sizeof(int*)); 
    arrHEAP[0] = (int *)malloc(5*sizeof(int));
    arrHEAP[1] = (int *)malloc(5*sizeof(int));
    for(int i=0;i<2;i++) // initialising
        for(int j=0;j<5;j++)
            arrHEAP[i][j] = num++;
    function_B(5, arrHEAP); // prints random data
    function_C(arrHEAP); // prints correctly, works

    // allocating STACK space for 2D array and initialising
    int arrSTACK[2][5] = {{100, 200, 300, 400, 500},{600,700,800,900,1000}};
    function_B(5, arrSTACK); // prints correctly, works
    //function_C(arrSTACK); // if I were to run this it crashes the program, why?
}
void function_B(int size, int (*arr)[size])
{
    for(int i=0;i<2;i++)
        for(int j=0;j<5;j++)
            printf("HEAP row is %d, value is %d:\n", i, arr[i][j]);
}
void function_C(int **arr)
{
    for(int i=0;i<2;i++)
        for(int j=0;j<5;j++)
            printf("HEAP row is %d, value is %d:\n", i, arr[i][j]);
}
int main()
{
    function_A(1);
}

Ответы [ 2 ]

3 голосов
/ 22 марта 2020

Преобразование массива / указателя

Недостаток, который вы понимаете, связан с использованием массивов и указателей. В C массив представляет собой отдельный тип объекта. Одна из причин, которая вызывает путаницу, заключается в том, что массив преобразуется в указатель на его первый элемент при доступе. (преобразование массива / указателя) Это регулируется C11 Standard - 6.3.2.1 Другие операнды - L-значения, массивы и указатели функций (p3) (обратите внимание на 4 исключения, когда преобразование массива / указателя не происходит)

Ключ здесь type . Когда вы объявляете двумерный массив, например,

int arrSTACK[2][5] = {{100, 200, 300, 400, 500},{600,700,800,900,1000}};

При доступе он будет преобразован в указатель - но какого типа? 2D массив в C - это массив одномерных массивов. Преобразование массива / указателя применяется только к первому уровню косвенности. Так при доступе arrSTACK преобразуется в указатель на массив int[5]. Так что его тип int (*)[5]. Так как тип управляет арифметика указателя c arrSTACK + 1 увеличивает пятизначные значения так, чтобы он указывал на начало второго 1D массива, который составляет arrSTACK (вторая строка)

Указатели

int **arrHEAP объявляет один указатель. A указатель на указатель на int. Это не имеет ничего общего с массивом. Однако указатель на указатель может быть проиндексирован, как если бы вы индексировали двумерный массив для адресации отдельных целых чисел, хранящихся в памяти. Это единственное сходство между двумерным массивом и объектом, созданным путем выделения памяти для указателей, а затем выделения памяти для целых чисел и назначения начального адреса для каждого блока, содержащего целые числа, одному из выделенных вами указателей. Здесь нет гарантии, что все элементы arrHEAP являются смежными в памяти, как и в двумерном массиве.

Итак, давайте посмотрим на разницу в том, как арифметика указателей c работает с arrHEAP. Когда вы разыменовываете arrHEAP, указатель на указатель (например, arrHEAP[0]) Какой тип возникает в результате разыменования? Если у вас есть pointer-to-pointer-to int и вы разыменовываете его, у вас остается pointer-to int. Так, с массивом, разыменование привело к типу pointer-to int[5], но с arrHEAP[0] результатом будет просто pointer-to int (no * 1056) * - это просто указатель на int). Так чем же отличается арифметика указателя c? arrSTACK + 1 увеличивает указатель на 5 * sizeof(int) байт (20 -байт). С arrHEAP + 1 продвигается только к следующему указателю в выделенном вами блоке указателей (1-указатель 8 -байт).

Именно поэтому вы не можете передать одну функцию другой , Функция, ожидающая, что массив воспринимает arrSTACK[0] и arrSTACK[1] на расстоянии 20 -байт, в то время как с указателем arrHEAP[0] и arrHEAP[1] разнесены только 8 -байт. Это суть предупреждений о несовместимости указателей и ошибок, которые вы генерируете.

Тогда нет гарантии, что все значения arrSTACK являются последовательными в памяти. Вы знаете, что arrSTACK[1] всегда 20 байтов от начала массива. С arrHEAP первый выделенный указатель не имеет гарантированных отношений с другим с точки зрения смежности. Позже они могут быть заменены или перераспределены.

Это означает, что если вы попытаетесь указать от arrSTACK до function_C(int **arr), компилятор сгенерирует предупреждение для несовместимых типов указателей - потому что они есть. И наоборот, если вы попытаетесь указать от arrHEAP до function_B(int size, int (*arr)[size]), оно также выдаст предупреждение из-за несовместимых типов указателей - потому что они есть.

Даже если использование объекта и массива в другой функции может показаться так, как будто оно будет работать, потому что вы по существу индексируете оба одинаково, компилятор не может пропустить один несовместимый тип - это не работа компиляторов.

Компилятор может основывать свою работу только на обещании, которое вы дали ему при написании кода. Для function_B(int size, int (*arr)[size]) вы обещали, что отправляете двумерный массив одномерных массивов, содержащий 5 int. С function_C(int **arr) вы пообещали компилятору предоставить указатель на указатель на int. Когда компилятор увидит, что вы пытаетесь передать неправильный объект в качестве параметра, он выдаст предупреждение, и вам следует учесть это предупреждение, поскольку начало 2-го блока целых чисел в arrHEAP не гарантированно равно 6 int от начала arrHEAP - и там его не будет.

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

В void function_B(int size (int (*arr)[size]), arr указывает на место, где есть некоторое количество строк с некоторым количеством int. Чтобы узнать, где находится какая-либо строка, компилятор должен знать, сколько int в каждой строке. Например, при 10 строках по 12 int строка 3 начинается после 3 • 12 int.

В void function_C(int **arr), arr указывает на место, где есть указатели на строки int. Чтобы узнать, где находится какая-либо строка, компилятор просто загружает один из этих указателей. Например, строка 3 начинается там, где указывает указатель arr[3].

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...