Выделение памяти для двумерного массива в C - PullRequest
2 голосов
/ 02 февраля 2011

Мне даны следующие структуры для создания моего кода:

struct Mtrx {
       unsigned double h;
       struct MtrxRows** mtrxrows;
}
struct MtrxRows {
       unsigned double w;
       double* row;
}

Я пытаюсь создать метод с именем mtrxCreate, который принимает параметры height и width, и это то, что я имею ниже:

Mtrx* mtrxCreate(unsigned double height, unsigned double width){
       Mtrx* mtrx_ptr = malloc(sizeof(double)*height);
       int i;
       mtrx_ptr->mtrxrows = malloc(sizeof(double)*height);
       for(i = 0; i < height; ++i){
              mtrx_ptr->mtrxrows[i]->row = malloc(sizeof(double) * width);
              mtrx_ptr->mtrxrows[i]->w = width;
       }
       mtrx_ptr->h = height;
       return mtrx_ptr;
}

Компилятор GCC сообщает мне, что у меня ошибка сегментации, поэтому я считаю, что неправильно распределил память. Я не уверен, какую память мне все еще нужно выделить, и если я выделю текущее количество на части матрицы выше, любая помощь приветствуется!

Ответы [ 3 ]

3 голосов
/ 02 февраля 2011

Ничего себе.Я не знаю точно, с чего начать очистку, поэтому я попытаюсь начать с нуля.

Из вашего кода кажется, что вы хотите, чтобы все строки и все столбцы были одинаковымиразмер - то есть никакие два ряда не будут иметь разные размеры.Если это не так, дайте мне знать, но это сделать гораздо сложнее.

Теперь давайте сначала определим struct для хранения количества строк, количества столбцов и самих данных массива.

struct Matrix {
  size_t width;
  size_t height;
  double **data;
};

Существуют различные способы хранения данных, но мы можем рассмотреть их позже.

size_t - целое число без знака (не с плавающей запятой - нет плавающих без знакатипы точек) тип, определенный в stddef.h (среди других мест), чтобы быть достаточно большим, чтобы хранить любой допустимый размер объекта или индекс массива.Поскольку нам нужно хранить размеры массивов, это именно то, что нам нужно для хранения высоты и ширины нашей матрицы.

double **data - это указатель на указатель на double, который (в данном случае) сложный способ сказать двумерный массив double s, который мы выделяем во время выполнения с помощью malloc.

Давайте начнем определять функцию.Все эти строки кода идут вместе, но я разделяю их, чтобы убедиться, что вы понимаете все различные части.

struct Matrix *make_Matrix(size_t width, size_t height, double fill)

Обратите внимание, что вы должны сказать struct Matrix, а не просто Matrix.Если вы хотите сбросить struct, вам придется использовать typedef, но это не так важно, ИМХО.Параметр fill позволит пользователю указать значение по умолчанию для всех элементов матрицы.

{
    struct Matrix *m = malloc(sizeof(struct Matrix));
    if(m == NULL) return NULL;

Эта строка выделяет достаточно памяти для хранения struct Matrix.Если он не может выделить память, мы возвращаем NULL.

    m->height = height;
    m->width  = width;
    m->data   = malloc(sizeof(double *) * height);
    if(m->data == NULL)
      {
        free(m);
        return NULL;
      }

Все, что должно иметь смысл.Поскольку m->data является double **, оно указывает на double * s, поэтому мы должны выделить количество объектов double * размера для хранения в нем.Если мы хотим, чтобы это была высота нашего массива, мы выделяем height число double * с, то есть sizeof(double *) * height.Помните: если ваш указатель T *, вам нужно выделить объекты T.

Если выделение не удастся, мы не можем просто вернуть NULL - это приведет к утечке памяти!Нам нужно free нашей ранее выделенной, но неполной матрице, прежде чем мы вернем NULL.

    for(size_t i = 0; i < height; i++)
      {
        m->data[i] = malloc(sizeof(double) * width);
        if(m->data[i] == NULL)
          {
            for(size_t j = 0; j < i; j++) free(m->data[j]);
            free(m->data);
            free(m);
            return 0;
          }

Теперь мы перебираем каждый столбец и выделяем строку.Обратите внимание, что мы выделяем sizeof(double) * width пробел - поскольку m->data[i] является double * (мы разыменовали double ** один раз), мы должны выделить double s для хранения в этом указателе.

Код для обработки ошибки malloc довольно сложен: мы должны вернуться к каждой ранее добавленной строке и free it, , затем free(m->data), затем free(m), а затем вернуть NULL.Вы должны освободить все в обратном порядке, потому что если вы сначала освободите m, то у вас не будет доступа ко всем данным m (и вам придется освободить все это, иначе вы потеряете память).

        for(size_t j = 0; j < width; j++) m->data[i][j] = fill;

Это перебирает все элементы строки и заполняет их значением fill.Не так уж плохо по сравнению с вышеописанным.

      }
    return m;
}

Как только все это будет сделано, мы просто возвращаем объект m.Теперь пользователи могут получить доступ к m->data[1][2] и получить элемент в столбце 2, строке 3. Но прежде чем мы закончим, так как создание заняло столько усилий, этот объект потребует немного усилий для очистки, когда мы закончим.Давайте сделаем функцию очистки:

void free_Matrix(struct Matrix *m)
{
    for(size_t i = 0; i < height; i++) free(m->data[i]);
    free(m->data);
    free(m);
}

Это делает (в основном) то, что мы должны были сделать в случае ошибки выделения в конструкторе (давайте продолжим и назовем его a), так что если вы получите все этоэто должен быть торт.

Следует отметить, что это не обязательно лучший способ реализации матрицы.Если вам требуется, чтобы пользователи вызывали функцию get(matrix, i, j) для доступа к массиву вместо прямой индексации данных через matrix->data[i][j], вы можете сжать (сложное) распределение double ** в плоский массив и вручную выполнить индексацию с помощью умножения в вашемфункции доступа.Если у вас есть C99 (или вы хотите перепрыгнуть через несколько циклов для поддержки C89), вы даже можете сделать плоские матричные данные частью вашего struct Matrix выделения объектов с помощью гибкого элемента массива, что позволит вам освободить свой объект однимпозвоните по номеру free.Но если вы понимаете, как работает вышеперечисленное, вы должны быть на пути к внедрению любого из этих решений.

3 голосов
/ 02 февраля 2011

Вы не выделяете нужное количество памяти для определенных вещей. Прежде всего, сама структура Mtrx:

Mtrx* mtrx_ptr = malloc(sizeof(double)*height);

Должно быть:

Mtrx* mtrx_ptr = malloc(sizeof(struct Mtrx));

Далее, я не уверен, почему ваше поле mtrxrows имеет двойной указатель. Я думаю, что это должен быть один указатель, одномерный массив строк (где каждая строка также содержит некоторое количество элементов). Если вы измените его на один указатель, вы должны выделить строки следующим образом:

mtrx_ptr->mtrxrows = malloc(sizeof(struct MtrxRows)*height);

Edit: Извините, я продолжаю замечать вещи в этом примере, поэтому я немного подправил ответ.

0 голосов
/ 02 февраля 2011

Как отмечает @Chris Lutz, проще начать с нуля. Как вы можете видеть из других ответов, обычно вы должны использовать целочисленный тип (например, size_t) для указания длины массива, и вы должны размещать не только указатели, но и структуры, в которых они хранятся. И еще одна вещь: вы всегда должны проверять результат выделения (если malloc вернул NULL). Всегда.

Идея: хранить двумерный массив в одномерном массиве

Что я хотел бы добавить: часто гораздо лучше хранить всю матрицу как непрерывный блок элементов и выполнять только одно выделение массива. Таким образом, структура матрицы становится примерно такой:

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

/* allocate a single contiguous block of elements */
typedef struct c_matrix_t {
    size_t w;
    size_t h;
    double *elems;  /* contiguos block, row-major order */
} c_matrix;

Преимущества:

  • Вы должны выделить память только один раз (как правило, медленная и непредсказуемая операция)
  • проще обрабатывать ошибки выделения (вам не нужно освобождать все ранее выделенные строки, если последняя строка не выделена, у вас есть только один указатель для проверки)
  • вы получаете непрерывный блок памяти, который может помочь эффективно написать некоторые матричные алгоритмы

Возможно, это также быстрее (но это должно быть проверено в первую очередь).

Недостатки:

  • вы не можете использовать m[i][j] нотацию и должны использовать специальные функции доступа (см. get и set ниже).

Получить / установить элементы

Вот они, функция для манипулирования такой матрицей:

/* get an element pointer by row and column numbers */
double* getp(c_matrix *m, size_t const row, size_t const col) {
    return (m->elems + m->w*row + col);
}

/* access elements by row and column numbers */
double get(c_matrix *m, size_t const row, size_t const col) {
    return *getp(m, row, col);
}

/* set elements by row and column numbers */
void set(c_matrix *m, size_t const row, size_t const col, double const val) {
    *getp(m, row, col) = val;
}

Распределение памяти

Теперь посмотрим, как вы можете выделить его, обратите внимание, насколько проще этот метод выделения:

/* allocate a matrix filled with zeros */
c_matrix *alloc_c_matrix(size_t const w, size_t const h) {
    double *pelems = NULL;
    c_matrix *pm = malloc(sizeof(c_matrix));
    if (pm) {
        pm->w = w;
        pm->h = h;
        pelems = calloc(w*h, sizeof(double));
        if (!pelems) {
            free(pm); pm = NULL;
            return NULL;
        }
        pm->elems = pelems;
        return pm;
    }
    return NULL;
}

Сначала мы выделяем матричную структуру (pm), и, если это распределение успешно, мы выделяем массив элементов (pelem). Поскольку последнее выделение может также потерпеть неудачу, мы должны откатить все выделение, которое мы уже сделали до этого момента. К счастью, при таком подходе есть только один из них (pm).

Наконец, мы должны написать функцию для освобождения матрицы.

/* free matrix memory */
void free_c_matrix(c_matrix *m) {
    if (m) {
    free(m->elems) ; m->elems = NULL;
    free(m); m = NULL;
    }
}

Поскольку исходный free (3) не выполняет никаких действий при получении указателя NULL, то ни наш free_c_matrix.

Тест

Теперь мы можем проверить матрицу:

int main(int argc, char *argv[]) {
    c_matrix *m;
    int i, j;
    m = alloc_c_matrix(10,10);
    for (i = 0; i < 10; i++) {
    for (j = 0; j < 10; j++) {
        set(m, i, j, i*10+j);
    }
    }
    for (i = 0; i < 10; i++) {
    for (j = 0; j < 10; j++) {
        printf("%4.1f\t", get(m, i, j));
    }
    printf("\n");
    }
    free_c_matrix(m);
    return 0;
}

Это работает. Мы можем даже запустить его через проверку памяти Valgrind и увидеть, что все в порядке. Нет утечек памяти.

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