是否有充分的理由说明结构中的指针不允许使用VLA?

时间:2015-09-14 13:09:11

标签: c pointers struct variable-length-array

这是一种定义矩阵类型的方法

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无法保证else if (( options.frozenColumn > -1 ) && ( i <= options.frozenColumn )) { **appendCellHtml(stringArrayL, row, i, colspan, d);** // The d variable is missing } 处理正确的值,但无论如何使用指针这都是危险的,所以它看起来不是一个好理由。

2 个答案:

答案 0 :(得分:2)

这符合您的要求吗?它在结构中存储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

答案 1 :(得分:1)

I believe that within a function in which a local variable nc is defined, you could use a typedef to create a local type double (*arr)[nc], and then cast a *double to that type. I believe such a cast would be legitimate for any *double that identifies a sufficiently-long sequence of double values, without regard for whether it was created using the same type as is defined within the function [if multiple functions each define their own array type, the compiler wouldn't recognize those types as equivalent, but it shouldn't matter]. I'm not 100% sure there wouldn't be Strict Aliasing issues, but I don't think there should be.

Otherwise, a fundamental difficulty is that a typedef involving a VLA creates a type using values that exist at a specific moment in time, and that can only occur for typedefs which are evaluated as executable statements, which in turn can only happen when typedefs are embedded within functions. Further, any identifiers used within array dimensions will be evaluated in the context of the enclosing function, rather than in the context of the partially-defined type.