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