Есть ли веская причина, почему VLA не разрешены в указателях в структурах?

Вот способ определить тип матрицы

typedef struct {
    int nr, nc;
    double *elem;
} Matrix;

Я хотел бы определить это

typedef struct {
    int nr, nc;
    double elem[nr][nc];
} Matrix;

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

Конечно, вышеупомянутое невозможно, хотя бы потому, что размер структуры не будет четко определен. Тогда я все равно был бы счастлив:

typedef struct {
    int nr, nc;
    double (*elem)[nc];
} Matrix;

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

Кажется, что это не разрешено ни одним стандартом, и я удивляюсь, почему, так как это легко сделать с помощью транстипинга. Например, используя первое определение (с double *), Я мог бы сделать

double get(Matrix *a, int i, int j) {
    int nc = a->nc;
    double (*p)[nc] = (double (*)[nc])a->elem;
    return p[i][j];
}

Конечно, это не очень интересно, так как есть только один доступ к элементу, но это может быть, если их много.

Итак, мой вопрос с надеждой, что это по теме: в чем причина запрета третьего определения?

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

2 ответа

Решение

Соответствует ли это вашим требованиям? Это хранит void * в структуре, и функции доступа приводят это к указателю на 2D VLA и используют это. GCC 5.2.0 в Mac OS X 10.10.5 компилирует его чисто, и valgrind (3.11.0-SVN от ноября 2014 года или около того) дает ему чистую оценку здоровья.

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

typedef struct
{
    int nr, nc;
    void *data;     // Actually double a[nr][nc]
} Matrix;

static double get(Matrix *a, int i, int j)
{
    double (*array)[a->nr][a->nc] = a->data;
    return (*array)[i][j];
}

static void set(Matrix *a, int i, int j, double v)
{
    double (*array)[a->nr][a->nc] = a->data;
    (*array)[i][j] = v;
}

static Matrix *mat_alloc(int nr, int nc)
{
    Matrix *m = malloc(sizeof(*m));
    if (m != 0)
    {
        m->nr = nr;
        m->nc = nc;
        m->data = malloc(nr * nc * sizeof(double));
        if (m->data == 0)
        {
            free(m);
            m = 0;
        }
    }
    return m;
}

static void mat_free(Matrix *m)
{
    free(m->data);
    free(m);
}

int main(void)
{
    int nr = 3;
    int nc = 5;

    Matrix *m = mat_alloc(nr, nc);
    if (m == 0)
    {
        fprintf(stderr, "Matrix allocation for %dx%d matrix failed\n", nr, nc);
        exit(1);
    }

    for (int i = 0; i < nr; i++)
    {
        for (int j = 0; j < nc; j++)
        {
            double v = (i * (nc + 1)) + j + 1;
            set(m, i, j, v);
            printf("Set: [%d,%d] = %4.1f\n", i, j, v);
        }
    }

    for (int j = 0; j < nc; j++)
    {
        for (int i = 0; i < nr; i++)
            printf("Get: [%d,%d] = %4.1f\n", i, j, get(m, i, j));
    }

    mat_free(m);
    return 0;
}

Я не уверен, есть ли аккуратный способ потерять (*array) часть обозначений в функциях доступа. Я бы предпочел, если бы он был (кроме использования array[0][i][j], то есть).

Пример запуска

Set: [0,0] =  1.0
Set: [0,1] =  2.0
Set: [0,2] =  3.0
Set: [0,3] =  4.0
Set: [0,4] =  5.0
Set: [1,0] =  7.0
Set: [1,1] =  8.0
Set: [1,2] =  9.0
Set: [1,3] = 10.0
Set: [1,4] = 11.0
Set: [2,0] = 13.0
Set: [2,1] = 14.0
Set: [2,2] = 15.0
Set: [2,3] = 16.0
Set: [2,4] = 17.0
Get: [0,0] =  1.0
Get: [1,0] =  7.0
Get: [2,0] = 13.0
Get: [0,1] =  2.0
Get: [1,1] =  8.0
Get: [2,1] = 14.0
Get: [0,2] =  3.0
Get: [1,2] =  9.0
Get: [2,2] = 15.0
Get: [0,3] =  4.0
Get: [1,3] = 10.0
Get: [2,3] = 16.0
Get: [0,4] =  5.0
Get: [1,4] = 11.0
Get: [2,4] = 17.0

Я считаю, что внутри функции, в которой локальная переменная nc определяется, вы можете использовать typedef для создания локального типа double (*arr)[nc], а затем бросить *double к этому типу. Я считаю, что такой акт был бы законным для любого *double который идентифицирует достаточно длинную последовательность double значений, независимо от того, был ли он создан с использованием того же типа, который определен в функции [если несколько функций определяют каждый свой собственный тип массива, компилятор не распознает эти типы как эквивалентные, но это не должно иметь значения]. Я не уверен на 100%, что не будет проблем со строгим псевдонимом, но я не думаю, что они должны быть.

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

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