Доступ к информации о 2d-массиве с помощью двойного указателя в структуре (C)

Пытаясь понять указатели, я создал массив M[x][y], указатель на указанный массив *p_M[x] и указатель на указанный указатель **d_p_M; первый указатель указывает на первый элемент строки в массиве M, а двойной указатель указывает на первую строку в *p_M;

Внутри функции результаты такие же, как и ожидалось, но при попытке доступа к элементам в main результаты становятся странными.

Может ли кто-нибудь помочь мне понять, что я делаю неправильно, пожалуйста?

Ниже приведен соответствующий код:

struct Matrix
{
    int rows;
    int cols;
    double **pMatrix;
};

struct Matrix unity_matrix(int row, int col);

int main()
{
    int row = 10, col = 10;
    struct Matrix m1 = unity_matrix(row, col);
    for (int x = 0; x < row; x++)
    {
        printf("\nOutside result %d\n", x);
        for (int y = 0; y < col; y++)
        {
            printf("%lf ", m1.pMatrix[x][y]);
        }
    }

    printf("\n Rows = %d Cols = %d", m1.rows, m1.cols);
    return 0;
}

struct Matrix unity_matrix(int row, int col)
{
    double v_mtrx[row][col], *p_v_mtrx[row];

    for (int x = 0; x < row; x++)
    {
        for (int y = 0; y < col; y++)
        {
            v_mtrx[x][y] = x + y + 1.0;
        }
    }

    for (int i = 0; i < row; i++) p_v_mtrx[i] = (double*) v_mtrx + i * col;

    struct Matrix mtrx = { row, col, (double **) p_v_mtrx
    };

    for (int x = 0; x < row; x++)
    {
        printf("\nInside result %d\n", x);
        for (int y = 0; y < col; y++)
        {
            printf("%lf ", mtrx.pMatrix[x][y]);
        }
    }

    return mtrx;
}

Внутренний результат 0

1,000000 2,000000 3,000000 4,000000

Внутренний результат 1

2.000000 3.000000 4.000000 5.000000

Внутренний результат 2

3.000000 4.000000 5.000000 6.000000

Внутренний результат 3

4.000000 5.000000 6.000000 7.000000

Внешний результат 0

1,000000 2,000000 3,000000 4,000000

Внешний результат 1

0,000000 -149953974918984380290969615723238406440144997002929093910432998988850442729636341287442401681195335938971907860 00787827379354468352.000000 4.000000 0.000000

Внешний результат 2

0,000000 0,000000 0,000000 0,000000

Внешний результат 3

0,000000 0,000000 0,000000 0,000000

2D-массив — это не то же самое, что массив указателей.

Barmar 09.08.2024 22:23

Необъявленные переменные области блока static (или extern) являются автоматическими — почти всегда «в стеке», хотя стандарт этого не требует — и автоматические переменные недействительны после выхода из блока (здесь после возврата функции) . Несмотря на использование более сложных типов, это то же самое, что stackoverflow.com/questions/1224042/… из 2009 года и stackoverflow.com/questions/11656532/returning-an-array-usin‌​g-c из 2012 года.

dave_thompson_085 09.08.2024 22:40

@dave_thompson_085 Я считаю, что он пытается вернуть сам массив, а не указатель на массив (который исчезнет при возврате), как вы это сделали бы для std::array в C++. В этом случае unity_matrix() возвращает Matrix, а не * Matrix, поэтому возврат не является контекстом указателя. Это не сработает, потому что при установке полей Matrix mtrx, p_v_mtrx является указателем на память стека и будет освобождена при возврате.

John Bayko 09.08.2024 23:03

В C принято передавать массив и размеры, которые будут изменены. Другой способ — явно выделить и освободить память, что открывает возможность утечек памяти.

John Bayko 09.08.2024 23:08

@JohnBayko да, OP возвращает struct, что действительно. Но он содержит указатель на локальный массив v_mtrx, который уходит из жизни.

Weather Vane 09.08.2024 23:14
Структурированный массив Numpy
Структурированный массив Numpy
Однако в реальных проектах я чаще всего имею дело со списками, состоящими из нескольких типов данных. Как мы можем использовать массивы numpy, чтобы...
T - 1Bits: Генерация последовательного массива
T - 1Bits: Генерация последовательного массива
По мере того, как мы пишем все больше кода, мы привыкаем к определенным способам действий. То тут, то там мы находим код, который заставляет нас...
Что такое деструктуризация массива в JavaScript?
Что такое деструктуризация массива в JavaScript?
Деструктуризация позволяет распаковывать значения из массивов и добавлять их в отдельные переменные.
1
5
70
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Ваш подход не работает, потому что указатель pMatrix структуры Matrix, возвращаемой unity_matrix, указывает на локальный массив p_v_mtrx этой функции. Это локальная переменная, которая отбрасывается, как только функция возвращает значение, поэтому доступ к ней из main имеет неопределенное поведение.

Вам следует выделить массивы из кучи и изменить API, чтобы можно было проверить наличие ошибок выделения.

Вот модифицированная версия:

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

typedef struct Matrix {
    int rows;
    int cols;
    double *values;
    double **pMatrix;
} Matrix;

bool init_unity_matrix(Matrix *m, int rows, int cols);

void free_matrix(Matrix *m) {
    free(m->values);
    m->values = NULL;
    free(m->pMatrix);
    m->pMatrix = NULL;
}

int main(void)
{
    int rows = 10, cols = 10;
    Matrix m;

    if (!init_unity_matrix(&m, rows, cols)) {
        printf("cannot allocate matrix\n");
        return 1;
    }
    printf("Unity matrix:\n");
    for (int x = 0; x < m.rows; x++) {
        for (int y = 0; y < m.cols; y++) {
            printf(" %10f", m.pMatrix[x][y]);
        }
        printf("\n");
    }
    free_matrix(&m);
    return 0;
}

bool init_unity_matrix(Matrix *m, int rows, int cols)
{
    m->rows = rows;
    m->cols = cols;
    m->values = calloc(sizeof(*m->values), (size_t)rows * cols);
    m->pMatrix = calloc(sizeof(*m->pMatrix), rows);
    if (!m->values || !m->pMatrix) {
        free_matrix(m);
        return false;
    }
    for (int y = 0; y < rows; y++) {
        m->pMatrix[y] = m->values + y * cols;
    }
    for (int y = 0; y < rows; y++) {
        for (int x = 0; x < cols; x++) {
            m->pMatrix[y][x] = y + x + 1.0;
        }
    }
    return true;
}

Вот альтернативный подход, при котором структура Matrix выделяется как один блок памяти и использует гибкий массив C99 для массива указателей строк pMatrix:

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

typedef struct Matrix {
    int rows;
    int cols;
    double *pMatrix[];
} Matrix;

Matrix *allocate_matrix(int rows, int cols)
{
    // compute size of structure and row array
    size_t size1 = sizeof(Matrix) + rows * sizeof(double *);
    // round up to align values array
    size_t values_offset = (size1 + sizeof(double) - 1) / sizeof(double);
    size_t total_size = sizeof(double) * (values_offset + (size_t)rows * cols);
    Matrix *m = calloc(1, total_size);
    if (m) {
        double *values = (double *)m + values_offset;
        m->rows = rows;
        m->cols = cols;
        for (int y = 0; y < rows; y++) {
            m->pMatrix[y] = values + y * cols;
        }
    }
    return m;
}

Matrix *unity_matrix(int rows, int cols)
{
    Matrix *m = allocate_matrix(rows, cols);
    if (m) {
        for (int y = 0; y < rows; y++) {
            for (int x = 0; x < cols; x++) {
                m->pMatrix[y][x] = y + x + 1.0;
            }
        }
    }
    return m;
}

void free_matrix(Matrix *m)
{
    free(m);
}

int main(void)
{
    int rows = 10, cols = 10;
    Matrix *m = unity_matrix(rows, cols);
    if (!m) {
        printf("cannot allocate matrix\n");
        return 1;
    }
    printf("Unity matrix:\n");
    for (int x = 0; x < m->rows; x++) {
        for (int y = 0; y < m->cols; y++) {
            printf(" %10f", m->pMatrix[x][y]);
        }
        printf("\n");
    }
    free_matrix(m);
    return 0;
}

Как отмечалось в комментариях, двумерный массив — это не то же самое, что массив указателей. Вам также необходимо учитывать время жизни. Массив, объявленный внутри функции, имеет автоматическое время существования и становится недействительным после возврата из функции.

Чтобы учесть это, вам необходимо динамически выделить массив внутри вашей структуры. С проверками, гарантирующими успех malloc, и кодом очистки, если это не так.

Например.

struct Matrix *allocMatrix(int rows, int cols) {
    struct Matrix *m = malloc(sizeof(struct Matrix));
    if (!m) return NULL;

    m->rows = rows;
    m->cols = cols;

    m->pMatrix = malloc(sizeof(double *) * rows);
    if (!m->pMatrix) {
        free(m);
        return NULL;
    }

    for (int i = 0; i < rows; i++) {
        m->pMatrix[i] = malloc(sizeof(double) * cols);
        if (!m->pMatrix[i]) {
            // Free the previously successfully allocated rows. 
            for (int j = 0; j < i; j++) {
                free(m->pMatrix[j]);
            }

            free(m);
            return NULL;
        }
    }
        
    return m;
}

Возвращая указатель на структуру, а не возвращая структуру по значению, вы можете:

  1. Избегайте копирования большого количества данных без необходимости.
  2. Верните NULL, чтобы указать на ошибку распределения.

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

Чтобы было более понятно, давайте сначала уточним, что происходит внутри функции unity_matrix.

В этой декларации

double v_mtrx[row][col], *p_v_mtrx[row];

это эквивалентно следующим объявлениям

double v_mtrx[row][col];
double * p_v_mtrx[row];

объявлены два массива: двумерный массив переменной длины v_mtrx и одномерный массив переменной длины p_v_mtrx с типом элемента double *.

После этого цикл for

for (int x = 0; x < row; x++)
{
    for (int y = 0; y < col; y++)
    {
        v_mtrx[x][y] = x + y + 1.0;
    }
}

массив v_mtrx выглядит так

1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 10.0
2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 10.0 11.0
3.0 4.0 5.0 6.0 7.0 8.0 9.0 10.0 11.0 12.0
4.0 5.0 6.0 7.0 8.0 9.0 10.0 11.0 12.0 13.0
5.0 6.0 7.0 8.0 9.0 10.0 11.0 12.0 13.0 14.0
and so on

В этом цикле for

for (int i = 0; i < row; i++) p_v_mtrx[i] = (double*) v_mtrx + i * col;

двумерный массив v_mtrx интерпретируется как одномерный массив с типом элемента double.

Теперь как инициализируются элементы, имеющие тип указателя double * массива p_v_mtrx?

Из-за арифметики указателя p_v_mtrx[0] установлено в &v_mtrx[0][0], p_v_mtrx[1] установлено в &v_mtrx[1][0], p_v_mtrx[2] установлено в &v_mtrx[2][0] и так далее.

В этом объявлении объекта структурного типа

struct Matrix mtrx = { row, col, (double **) p_v_mtrx
};

список инициализаторов эквивалентен следующему

struct Matrix mtrx = { row, col, p_v_mtrx
};

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

Поскольку тип элемента массива p_v_mtrx равен double *, то указатель на его первый элемент будет иметь тип double **.

Теперь давайте рассмотрим, что происходит в этих вложенных циклах for.

for (int x = 0; x < row; x++)
{
    printf("\nInside result %d\n", x);
    for (int y = 0; y < col; y++)
    {
        printf("%lf ", mtrx.pMatrix[x][y]);
    }
}

Выражение pMatrix[0] возвращает первый элемент массива p_v_mtrx, который, в свою очередь, указывает на элемент v_mtrx[0][0] из-за его инициализации выражением &v_mtrx[0][0], как показано выше. Таким образом, благодаря арифметике указателей, когда x равно 0, выводится первая строка двумерного массива v_mtrx, то есть значений выражений mtrx.pMatrix[0][y], соответствующих значениям выражений v_mtrx[0][y]. И так далее, например, выводятся значения выражений mtrx.pMatrix[1][y], соответствующие значениям выражений v_mtrx[1][y].

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

Вам нужно динамически распределять массивы, как показано в других ответах.

Другие вопросы по теме