在C语言中初始化变量大小的数组。

4

我尝试定义一个包含可变大小数组成员的结构体类型,就像这样:

typedef struct {
  const int M;
  const int N;
  int       x[];    // Should be 1-D M elements
  int       y[][];  // 2-D M*N elements
} xy_t;

可变大小数组的原因是我有一个应该在可变维度上工作的函数。
然而,这会导致错误,所以我进行了重写:

typedef struct {
  const int M;
  const int N;
  int       *x;    // 1-D M elements
  int       **y;   // 2-D M* elements
} xy_t;

这些代码可以编译通过。但问题是,我该如何对其进行初始化?

static xy_t xy = {.M = 3, .N = 2, .x = ???, .y = ???};

.x = (int[3]){0} 看起来可以工作,但我还没找到给 y 赋值的方法。

我尝试了 .y = (int[3][2]){{0,0}, {0,0}, {0,0}} 以及几个类似的变体都没有成功。


2
不知道你需要用于什么,很难说哪种方案是最好的。例如,一个作为1D数组访问,然后是2D数组的单个灵活数组成员,可能既合理又笨拙。你需要元素在内存中相邻分配吗,还是完全没有关系?此外,在运行时调整大小是否可行? - Lundin
1
为什么不编写一个函数,以适当的动态分配返回 xy_t 实例呢?比如 xy_t xy_t_create(unsigned int m, unsigned int n); 你还需要一个清理函数来释放数组。 - no more sigsegv
xy 都是指针,所以 {.x=(int *)0, .y=(int**)0} 可以正常工作,然后它们可以分配给要处理的数组。或者,创建一个一维数组,例如 x1[M]={0},和一个二维数组,例如 y2[M][N]={{0}},然后分别使用这两个数组初始化 x 和 y。 - user6099871
2
@AlbertShown 我真的以为那些非常烦人的“C语言没有可变长度数组”垃圾评论会随着时间的推移而减少。但是在C语言引入可变长度数组24年后,它们仍然很流行... - Lundin
2
@dbush,这是在1999年至2011年间的强制功能。在2012年至2023年期间为可选功能。从2023年开始,指向VLA的指针再次成为强制性功能,允许VLA类型的对象仍然是可选的。 - Lundin
显示剩余9条评论
4个回答

2
你可以将成员y作为不完整数组类型的指针。
typedef struct {
  ...
  int       (*y)[]; // a pointer an array of unspecified length
} xy_t;

这将允许使用复合字面量初始化y

xy_t xy;
xy.y = (int[3][2]){{0,0}, {0,0}, {0,0}};

然而,无法取消引用此二维数组,因为xy.y的类型是不完整的。可以通过将此指针分配给已完成类型的VLA指针来解决此问题。

int (*arr)[xy.N] = xy.y;

arr[i][j] = 42;

我认为你的代码不符合C标准,而是C ++。但他的问题是关于C的。 - Noob
@Noname,不是的。它就是普通的C99。你为什么认为它是C++呢? - tstanisl
@Noname,它在严格模式下(https://godbolt.org/z/5o5j5T5P7)完美编译。它甚至还能很好地优化代码。 - tstanisl
好的,获取 +1 然后 :) - Noob

1

int **不再是一个二维数组; 它是一个指向指针的指针,或者更具体地说,在我们的情况下是指向指针数组的指针。

这就是为什么您在将指针分配给2D数组 ((int[3][2]){{0,0}, {0,0}, {0,0}}) 时会遇到问题的原因。

要初始化int**,您需要类似于以下内容:

xy_t xy = {
   .M = 3,
   .N = 2,
   .x = (int[3]){ 0,0,0 },
   .y = (int*[3]){
      (int[2]){ 0,0 },
      (int[2]){ 0,0 },
      (int[2]){ 0,0 },
   },
};

你也可以让编译器来计数:
xy_t xy = {
   .M = 3,
   .N = 2,
   .x = (int[]){ 0,0,0 },
   .y = (int*[]){
      (int[]){ 0,0 },
      (int[]){ 0,0 },
      (int[]){ 0,0 },
   },
};

尽管不是二维数组,仍然可以使用xy.y[ i ][ j ]来访问y[i][j]。


上述结构需要M+2个指针和M+3个内存块。如果您想要使结构更小,可以使用以下结构,它只使用了2个指针和3个内存块:

typedef struct {
  const int M;
  const int N;
  int       *x;   // Should be 1-D M elements
  int       *y;   // 2-D M*N elements
} xy_t;

xy_t xy = {
   .M = 3,
   .N = 2,
   .x = (int[]){ 0,0,0 },
   .y = (int[]){ 0,0, 0,0, 0,0 },
};

要访问y [i] [j],您可以使用xy.y [i * xy.N + j]

您还可以将其强制转换为指向2D数组的指针,以便使用y [i] [j]。操作如下:

int (*y)[ xy.M ][ xy.N ] = (int(*)[ xy.M ][ xy.N ])xy.y;

你甚至可以让它更小!以下代码使用零指针和仅一个内存块。

typedef struct {
  const int M;
  const int N;
  int xy[];
} xy_t;

xy_t xy = {
   .M = 3,
   .N = 2,
   .xy = {
       0,0,0,
       0,0, 0,0, 0,0
   },
};

你可以使用xy.xy[ i ]来访问x[i]。
你可以使用xy.xy[ xy.M + i * xy.N + j ]来访问y[i][j]。

或者:

int (*x)[ xy.M ]         = (int(*)[ xy.M ])xy.xy;
int (*y)[ xy.M ][ xy.N ] = (int(*)[ xy.M ][ xy.N ])( xy.xy + xy.M );

x[i]
y[i][j]

话虽如此,gcc发出警告

警告:初始化灵活数组成员[-Wpedantic]

因此,这个最后的解决方案很可能是非标准的。


我在我的回答中添加了很多内容。 - ikegami

0

在C语言中没有简单的方法。即使在C++中也没有简单的方法:)

你必须要么:

  1. 声明一个完全初始化的二维数组,并将其名称用作.y的初始化器。
  2. 几乎相同,但是使用主维度(指针数组)的malloc和另一个malloc(或多个)来为次要维度分配内存(指针数组中的每个指针)。这就是上面提到的Tristan Riehs的方法。

然而,如果你愿意放弃两个维度的可调整大小性,你可以使用宏来模拟C++的模板,以便你结构体的每个具体实例都有固定的第二维。


0
如果你想把xy初始化为0,你可以创建一个使用malloc来初始化它们的函数。
typedef struct {
  const int M;
  const int N;
  int      *x;  // 1-D M elements
  int      *y;  // 2-D M*N elements
} xy_t;

void init_arrays(xy_t *xy){
    int *new_x = malloc(sizeof(int)*(xy->M));
    for (int i = 0; i < xy->M; i++) {
        new_x[i] = 0;
    }
    xy->x = new_x;

    int *new_y = malloc(sizeof(int)*(xy->M*xy->N));
    for (int i = 0; i < xy->M; i++) {
        for (int j = 0; j < xy->N; j++) {
            new_y[i*xy->N + j] = 0;
        }
    }
    xy->y = new_y;
}

这是一个例子,你可能更喜欢创建像这样的构造函数:xy_t *new_xy_t(int M, int N),两者都可以工作。

NB:

  • y 已经变成了 1-D,2-D 是通过 i*xy->M + j(如 klutt 所提到的)人为创建的,这使得操作更加安全。您可以创建一个 getter 和 setter 更轻松地访问数字。
  • 由于我们使用了动态分配,因此必须配合一个 free 函数(如 no more sigsev 所提到的)。

如果你想推荐分配和零初始化,那么为什么不使用calloc函数呢? - Adrian Mole
@AdrianMole,我不知道calloc这个函数。谢谢您的建议,使用它可以缩短代码。 - Tristan Riehs

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接