【问题标题】:Is there a good reason why VLA are not permitted in pointers in structs?结构中的指针中不允许使用 VLA 是否有充分的理由?
【发布时间】:2015-12-10 12:10:47
【问题描述】:

这是一种定义 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 处理正确的值,但无论如何这对于指针来说都是危险的,所以这看起来不是一个好的理由。

【问题讨论】:

  • 问题是,除了包含灵活数组成员的结构体之外,结构体的大小在编译时是固定的,这样编译器就知道如何布局结构体的数组,例如.
  • @JonathanLeffler 这就是第二个定义错误的原因。但是第三个使用指针,所以大小是已知的(双精度不是结构的一部分)。它只是向double * 添加一些信息,以便透明地完成算术。
  • 灵活数组成员中类型的大小必须在编译时知道,而不是在您尝试将nc 构建到类型时。
  • @EliasVanOotegem:括号很重要。这是double * 数组和double 数组指针之间的区别。
  • @Jean-ClaudeArbaut: double (*a)[nc] 是一个指向一维数组的指针,但你说得对,括号很重要。您的问题是 nc 不能用作结构中数组的维度,即使它可以与结构外的局部变量或动态分配的变量(数组)一起使用。

标签: c pointers struct variable-length-array


【解决方案1】:

这是否符合您的要求?它将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

【讨论】:

  • 我尝试过这样的事情,但认为它不是很干净:array[0][i][j] 让我将其视为 3d 数组。使用指针作为第一个索引有什么问题吗?但它不允许绑定检查。
  • 我同意array[0][i][j] 具有“误导性”;这就是使用(*array)[i][j] 编写代码的原因。我不清楚您所说的“它不允许绑定检查”是什么意思。验证 ija-&gt;nra-&gt;nc 是微不足道的 - 事实上,我最初在标题中有 #include &lt;assert.h&gt; 以进行验证。我同意这并不完全可取。但是,编写一个矩阵乘法函数是完全可行的,例如,它接受两个矩阵并返回一个适当大小的结果矩阵,而主体将只使用矩阵表示法。
  • 我的意思是,聪明的编译器可以在编译时或运行时使用已知的边界(nrnc)检查边界。但是,当第一个维度是指针时,您对此一无所知:它就像一个宽度为nc 的无限数组。这是因为,在 C 中,a[n] 在 a 是数组和指针时都有效(顺便说一句,n[a] 同样有效)。
【解决方案2】:

我相信在定义了局部变量 nc 的函数中,您可以使用 typedef 创建局部类型 double (*arr)[nc],然后将 *double 强制转换为该类型。我相信这样的转换对于任何识别足够长的double 值序列的*double 都是合法的,而不管它是否是使用函数中定义的相同类型创建的[如果多个函数各自定义它们的自己的数组类型,编译器不会将这些类型识别为等效的,但没关系]。我不能 100% 确定不会有严格别名问题,但我认为不应该有。

否则,一个根本的困难是涉及 VLA 的 typedef 使用在特定时间存在的值创建类型,并且只能在被评估为可执行语句的 typedef 中发生,而这又只能在以下情况下发生typedef 嵌入在函数中。此外,数组维度中使用的任何标识符都将在封闭函数的上下文中进行评估,而不是在部分定义类型的上下文中。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-22
    • 1970-01-01
    • 2013-06-04
    • 2010-09-16
    • 2018-02-11
    相关资源
    最近更新 更多