Есть ли веская причина, почему 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 .

Кажется, что это запрещено стандартом, и мне интересно, почему, так как легко сделать то же самое путем transtyping. Например, используя первое определение (с 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 обрабатывает правильное значение, но это все равно опасно с указателями, поэтому это не похоже на вескую причину.

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

 #include  #include  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, создает тип, используя значения, которые существуют в определенный момент времени, и это может произойти только для typedefs, которые оцениваются как исполняемые операторы, что, в свою очередь, может произойти только при вводе typedefs внутри функций. Кроме того, любые идентификаторы, используемые в измерениях массива, будут оцениваться в контексте закрывающей функции, а не в контексте частично определенного типа.