结构中的指针中不允许使用 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 嵌入到函数中时会发生。此外,数组维度中使用的任何标识符都将在封闭函数的上下文中进行评估,而不是在部分定义的类型的上下文中进行评估。