结构中的指针中不允许使用 VLA 有充分的理由吗?
Is there a good reason why VLA are not permitted in pointers in structs?
这是一种定义 Matrix 类型的方法
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];
}
当然,这里不是很有趣,因为只有一个访问elem,但如果有很多的话,也可能是。
所以,我的问题,希望它切题:禁止第三个定义的真正原因是什么?
我可以想象这很危险,因为不能保证 nc
处理正确的值,但无论如何这对于指针来说都是危险的,所以它看起来不是一个好的理由。
这符合您的要求吗?它在结构中存储一个 void *
,访问函数将其转换为指向 2D VLA 的指针并使用它。 Mac OS X 10.10.5 上的 GCC 5.2.0 可以干净地编译它,并且 valgrind
(2014 年 11 月左右的 3.11.0-SVN)给它一个干净的健康证明。
#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% 确定不会有严格的别名问题,但我认为不应该有。
否则,一个根本的困难是涉及 VLA 的 typedef 使用在特定时刻存在的值创建类型,并且只能发生在被评估为可执行语句的 typedef 中,而这反过来只能当 typedef 嵌入到函数中时会发生。此外,数组维度中使用的任何标识符都将在封闭函数的上下文中进行评估,而不是在部分定义的类型的上下文中进行评估。
这是一种定义 Matrix 类型的方法
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];
}
当然,这里不是很有趣,因为只有一个访问elem,但如果有很多的话,也可能是。
所以,我的问题,希望它切题:禁止第三个定义的真正原因是什么?
我可以想象这很危险,因为不能保证 nc
处理正确的值,但无论如何这对于指针来说都是危险的,所以它看起来不是一个好的理由。
这符合您的要求吗?它在结构中存储一个 void *
,访问函数将其转换为指向 2D VLA 的指针并使用它。 Mac OS X 10.10.5 上的 GCC 5.2.0 可以干净地编译它,并且 valgrind
(2014 年 11 月左右的 3.11.0-SVN)给它一个干净的健康证明。
#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% 确定不会有严格的别名问题,但我认为不应该有。
否则,一个根本的困难是涉及 VLA 的 typedef 使用在特定时刻存在的值创建类型,并且只能发生在被评估为可执行语句的 typedef 中,而这反过来只能当 typedef 嵌入到函数中时会发生。此外,数组维度中使用的任何标识符都将在封闭函数的上下文中进行评估,而不是在部分定义的类型的上下文中进行评估。