Как инициализировать динамический 2D-массив внутри структуры в C? - PullRequest
0 голосов
/ 15 февраля 2019

Я хочу использовать структуру, чтобы содержать некоторые данные и передавать их между различными функциями в моей программе, эта структура должна содержать динамический 2D-массив (мне нужна матрица), размеры которого изменяются в зависимости от аргументов программы.Итак, это моя структура:

    struct mystruct {
        int **my2darray;

    }

У меня есть функция, которая читает числа из файла и должна назначить каждый из них в ячейку массива структуры.

Я пытался сделать это:

    FILE *fp = fopen(filename, "r");
    int rows;
    int columns;
    struct mystruct *result = malloc(sizeof(struct mystruct));
    result->my2darray = malloc(sizeof(int)*rows); 
    int tmp[rows][columns];
    for(int i = 0;i<rows;i++) {
        for(int j = 0;j<columns;j++) {
            fscanf(fp, "%d", &tmp[i][j]); 
        }
        result->my2darray[i]=malloc(sizeof(int)*columns);
        memcpy(result->my2darray[i],tmp[i],sizeof(tmp[i]));
    }

Но это дает мне странный результат: все строки сохранены правильно, кроме первой.(Я уверен, что проблема не в проверке файла).Хотя, если я изменю четвертую строку кода в этом:

    result->my2darray = malloc(sizeof(int)*(rows+1)); 

, все будет работать нормально.Теперь мой вопрос: почему это происходит?

Ответы [ 3 ]

0 голосов
/ 15 февраля 2019

Приведенный выше код никогда не устанавливает rows и columns, поэтому при чтении этих значений код имеет неопределенное поведение .

Если вы правильно установите эти значения, это не таквыделение нужного количества памяти:

result->my2darray = malloc(sizeof(int)*rows);

Вы фактически выделяете пространство для массива int вместо массива int *.Если последний больше (и, скорее всего, так), то вы не выделили достаточно места для массива, и вы снова вызываете неопределенное поведение, записывая после конца выделенной памяти.

Вы можете выделить нужное количествопространства, как это:

result->my2darray = malloc(sizeof(int *)*rows);

Или даже лучше, поскольку это не зависит от фактического типа:

result->my2darray = malloc(sizeof(*result->my2darray)*rows);

Кроме того, нет необходимости создавать временный массив для чтениязначения в.Просто прочитайте их прямо в my2darray:

for(int i = 0;i<rows;i++) {
    result->my2darray[i]=malloc(sizeof(int)*columns);
    for(int j = 0;j<columns;j++) {
        fscanf(fp, "%d", &result->my2darray[i][j]); 
    }
}
0 голосов
/ 15 февраля 2019

Вот ответ, использующий некоторые «новые» возможности языка: гибкие элементы массива и указатели на VLA.

Прежде всего, пожалуйста, отметьте Правильное размещение многомерных массивов .Вам понадобится 2D-массив, а не какая-нибудь справочная таблица.

Чтобы выделить такой истинный 2D-массив, вы можете использовать гибкие элементы массива:

typedef struct
{
  size_t x;
  size_t y;
  int flex[];
} array2d_t;

Он будет выделен какистинный массив, хотя и "искаженный" в одном измерении:

size_t x = 2;
size_t y = 3;
array2d_t* arr2d = malloc( sizeof *arr2d + sizeof(int[x][y]) );

Поскольку проблема с гибкими элементами массива состоит в том, что они не могут быть ни VLA, ни 2-мерными.И хотя приведение его к другому целочисленному типу массива безопасно (в отношении псевдонимов и выравнивания), синтаксис довольно злой:

int(*ptr)[y] = (int(*)[y]) arr2d->flex;  // bleh!

Можно было бы скрыть весь этот злой синтаксис за макросом:

#define get_array(arr2d) \
  _Generic( (arr2d),     \
            array2d_t*: (int(*)[(arr2d)->y])(arr2d)->flex )

Читайте как: если arr2d имеет тип array2d_t*, тогда получите доступ к этому указателю, чтобы получить член flex, а затем приведите его к указателю массива соответствующего типа.

Полный пример:

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

typedef struct
{
  size_t x;
  size_t y;
  int flex[];
} array2d_t;

#define get_array(arr2d) \
  _Generic( (arr2d),     \
            array2d_t*: (int(*)[(arr2d)->y])(arr2d)->flex )

int main (void)
{
  size_t x = 2;
  size_t y = 3;

  array2d_t* arr = malloc( sizeof *arr + sizeof(int[x][y]) );
  arr->x = x;
  arr->y = y;


  for(size_t i=0; i<arr->x; i++)
  {
    for(size_t j=0; j<arr->y; j++)
    {
      get_array(arr)[i][j] = i+j;
      printf("%d ", get_array(arr)[i][j]);
    }
    printf("\n");
  }

  free(arr);
  return 0; 
}

Преимущества перед указателем на указатель:

  • Фактический двумерный массив, который может быть выделен / освобожден одним вызовом функции и может быть переданк функциям, таким как memcpy.

    Например, если у вас есть два array2d_t*, указывающие на выделенную память, вы можете скопировать все содержимое одним вызовом memcpy, без необходимости доступа к отдельным элементам.

  • Без дополнительныхбеспорядок в структуре, только массив.
  • При обращении к массиву отсутствует кэш-память из-за сегментирования памяти по всей куче.
0 голосов
/ 15 февраля 2019

В предоставленном вами примере кода переменные rows и columns не были инициализированы перед использованием, поэтому они могут содержать что угодно, но, скорее всего, будут равны 0. В любом случае, как написано, результаты всегда будутбыть непредсказуемым.

Когда в C необходим массив 2D , полезно инкапсулировать распределение памяти и освобождение памяти в функции, чтобы упростить задачу и улучшить читаемость.Например, в вашем коде следующая строка создаст массив из 5 указателей, каждый из которых будет указывать на 20 int мест хранения: (создание 100 адресуемых адресов int мест.)

int main(void)
{
    struct mystruct result = {0}; 

    result.my2darray = Create2D(5, 20);

    if(result.my2darray)
    {
        // use result.my2darray 
        result.my2darray[0][3] = 20;// for simple example, but more likely in a read loop                         
        // then free result.my2darray
        free2D(result.my2darray, 5);
    }
    return 0;
}

Использование следующихдве функции:

int ** Create2D(int c, int r)
{   
    int **arr;
    int    y;

    arr   = calloc(c, sizeof(int *)); //create c pointers (columns)
    for(y=0;y<c;y++)
    {
        arr[y] = calloc(r, sizeof(int)); //create r int locations for each pointer (rows)
    }
    return arr;
}

void free2D(int **arr, int c)
{
    int i;
    if(!arr) return;
    for(i=0;i<c;i++)
    {
        if(arr[i]) 
        {
            free(arr[i]);
            arr[i] = NULL;
        }
    }
    free(arr);
    arr = NULL;
}

Имейте в виду, что то, что вы создали с помощью этой техники, на самом деле представляет собой 5 различных позиций указателя, каждое из которых указывает на набор из 20 int положений.Это то, что облегчает использование массива, как индексация, то есть мы можем сказать, что result.my2darray[1][3] представляет второй столбец , четвертая строка элемент массива 5X20, когда он на самом деле не является массивомвообще.

int some_array[5][20] = {0};//init all elements to zero

- это , что обычно упоминается в массиве C * int, также позволяя доступ к каждому элементу посредством индексации.В действительности (хотя обычно упоминается как массив.) Это не массив.Расположение элементов в этой переменной хранится в одном смежном месте в памяти.

|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0...  (~ 82 more)

Но C поддерживает местоположения так, что все они индексируются как двумерный массив.

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