Динамическое распределение функции 2d-массива

Итак, у меня есть программа на С, структурированная в 3 файла: main.c, alloc.h и alloc.c. В функции main.c у меня есть объявление указателя на другой указатель, которому я намерен выделить массив n * m:

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

int main() {
   int **mat, n, m;
   alloc_matrix(&mat, int &n, int &m);
   return 0;
}

В alloc.c у меня есть следующие объявления:

#ifndef ALLOC_H_INCLUDED
#define ALLOC_H_INCLUDED
#include <stdio.h>
#include <stdlib.h>
void alloc_matrix(int***, int*, int*);
#endif

В alloc.c у меня есть функция:

void alloc_matrix(int ***mat, int *n, int *m) {
    printf("\nn = "); scanf("%d", n);
    printf("\nm = "); scanf("%d", m);
    *mat = (int**)calloc(*n, sizeof(int*));
    int i;
    for (i = 0; i < *n; i++)
        *(mat + i) = (int*)calloc(*m, sizeof(int));
}

Но программа не работает. Он входит в какую-то петлю и не заканчивается. Если я выделю его в main, он будет работать, но я понятия не имею, что я делаю неправильно в функции alloc.

Ответ 1

Вот правильный код. Ваша ошибка заключалась в том, что в определении alloc_matrix вы использовали *(mat+i) в цикле выделения, который должен быть *(*mat+i), поскольку mat - это int***, поэтому базовый адрес для 2D-массива будет находиться в *mat, Затем вам нужно переместить смещение i, а затем удалить ссылку на эту ячейку памяти для 1D-массива.

Main:

#include <stdio.h>
#include <stdlib.h>
#include "alloc.h"
int main()
{
   int **mat,n,m;
   alloc_matrix(&mat,&n,&m);
   return 0;
}

alloc.h

#ifndef ALLOC_H_INCLUDED
#define ALLOC_H_INCLUDED
#include <stdio.h>
#include <stdlib.h>
void alloc_matrix(int***,int*,int*);

#endif

alloc.c:

void alloc_matrix(int ***mat,int *n,int *m)
{
    printf("\nn = "); scanf("%d", n);
    printf("\nm = "); scanf("%d", m);
    *mat = (int**)calloc(*n,sizeof(int*));
    int i;
    for(i = 0; i < *n; i++)
    *(*mat+i) = (int*)calloc(*m,sizeof(int));
}

Код для функции чтения:

void read_matrix(int ***mat,int n,int m)
    {
      int i,j;
      for(i = 0; i < n; i++)
       for(j = 0; j < m; j++)
        {
          printf("mat[%d][%d] = ", i, j);
          scanf("%d", (*(*mat+i))+j);
        }
    }

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

Ответ 2

void alloc_matrix(int ***mat,int *n,int *m)

В этой строке есть две проблемы. Ни один из них не смертелен, но оба стоит исправить.

Первая проблема: матрица в этой программе представлена как int**. Почему alloc_matrix принимает int***? Все стандартные функции, которые выделяют что-то (malloc и friends), возвращают указатель на что-то. Это идиоматический способ ведения дел в Си. Он уменьшает количество звезд (быть трехзвездным программистом Си - это не достижение, которым можно гордиться) и упрощает код. Функция должна быть изменена на

int** alloc_matrix( // but what inside the () ?

Вторая проблема заключается в том, почему функция с именем alloc_matrix запрашивает пользователя и читает значения? Эти вещи не связаны с распределением. Функция должна делать одну вещь и делать это хорошо. malloc предлагает вам ввести размер? fopen предлагает вам ввести имя файла? Эти вещи будут рассматриваться как глупость первой степени, и это правильно. Рекомендуется прочитать размеры в другом месте и передать их alloc_matrix в качестве входных аргументов. Следовательно,

int** alloc_matrix(int n, int m) { // but what inside the {}?

То, что осталось от alloc_matrix, просто:

int** alloc_matrix(int n, int m) {
  int** mat; // that what we will return
  int i;
  mat = (int**)calloc(n, sizeof(int*));
  for(i = 0; i < n; i++)
     // here comes the important part.

Поскольку мы упростили alloc_matrix и уменьшили количество звезд в mat, что нам делать со старым телом циклы? Это было:

    *(mat+i) = (int*)calloc(...);

но если мы удалим звезду, она станет

    (mat+i) = (int*)calloc(...);

что является очевидной ерундой. Возможно, старая линия была проблемой. Тот факт, что он вызвал предупреждение компилятора, безусловно, не говорит о его правильности. Так как это исправить? Там не так много вариантов. Оказывается, чтобы восстановить здравомыслие, мы должны оставить старую левую сторону (написано для трех звезд mat) нетронутой. Или, что еще лучше, используйте эквивалентную, но более идиоматическую запись:

    mat[i] = (int*)calloc(m, sizeof(int));

Таким образом, вся функция теперь становится

int** alloc_matrix(int n, int m) {
  int **mat;
  int i;
  mat = (int**)calloc(n, sizeof(int*));
  for(i = 0; i < n; i++)
    mat[i] = (int*)calloc(m, sizeof(int));
  return mat; 
}

и он должен называться как

mat = alloc_matrix(n, m);

Часто говорят, что нельзя бросать результат calloc и друзей. Но в этом случае приведение включило предупреждение, которое помогло найти ошибку. Сейчас я оставляю слепки на месте.

Существует другая идиома для распределения, которая не требует приведения, но также позволяет избежать проблемы несоответствия типов. Вместо использования типа для sizeof вы можете использовать разыменованный указатель, поскольку информация о типе доступна в переменной:

mat = (int**)calloc(n, sizeof(int*));

можно изменить на

mat = calloc(n, sizeof *mat); //sizeof is an operator not a function