c语言 - 在结构中需要一个"variable"大小的二维数组



我正在尝试实现一个单元格网格,类似于康威的生命游戏。

虽然每个单独的网格在两个维度上都应该有固定的大小,但我想要一个允许在两个维度上任何大小的网格结构。

这类似于数组可以是任何大小的,但是一旦初始化的数组具有固定的大小。

这是我到目前为止所拥有的:

typedef struct Cell {
    int data;
    // stuff to be added later
} Cell;
typedef struct Grid {
    unsigned width;
    unsigned height;
    Cell cell[][];
} Grid;
Grid initGrid(unsigned width, unsigned height) {
    Grid g;
    g.width = width;
    g.height = height;
    g.cell = malloc( sizeof(Cell)*width*height );
    return g;
}

但是我收到以下编译时错误:

main.c|12|note: declaration of `‘cell’ as multidimensional array must have bounds for all dimensions except the first|

如何定义具有灵活大小的Grid数据类型?

后记:作为 C 新手,我认为以下内容会起作用:

typedef struct Grid {
    unsigned width;
    unsigned height;
    Cell cell[width][height];
} Grid;

后记:每当我使用malloc时,我总是感到不安。我在这里做(或试图做(任何可怕的错误吗?

你不能用

C 中的双索引 ( cell[x][y] ( 来做到这一点,没有办法表达每行要跳转的字节数是动态的。

因此,最好的(在我看来(方法是使用一维数组手动进行索引。

放一个普通:

Cell *cell;

struct(保持widthheight(,然后像这样索引:

set_cell(Grid *g, unsigned int x, unsigned int y, Cell value)
{
  g->cell[y * g->width + x] = value;
}

编译器不太可能内联它,而且它会非常紧凑。可能比锯齿数组更快"方法,该方法使用更多的内存和另一层间接寻址。

分配很简单:

Grid initGrid(unsigned int width, unsigned int height)
{
    Grid g;
    g.width = width;
    g.height = height;
    g.cell = malloc(width * height * sizeof *g.cell);
    // add assert or error here, can't return NULL for value type
    return g;
}

如果你也想堆分配Grid,你可以把它和它的元素共同分配。

是的,您需要在完成分配后free()分配,以免泄漏内存。严格来说,在现代系统上,操作系统将在程序结束时释放所有资源,但无论如何释放都是很好的形式:

void destroyGrid(Grid g)
{
  free(g.cell);
}

跟随这篇优秀的帖子:如何在 C 中使用动态多维数组?阅读@JensGustedt帖子并关注他的链接可变长度数组 (VLA(

实际上有一种方法 - 我关注了他的帖子并编写了一个小型测试程序来验证:

#include <stdio.h>
#include <stdlib.h>
int main(int argc, char ** argv)
{
    unsigned int height = 100;
    unsigned int width = 10;
    int (*array)[width] = malloc (sizeof(int[height][width]));
    array[90][2] = 1231;
    printf("%d", array[90][2]);
}

#include <stdio.h>
#include <stdlib.h>
int main(int argc, char ** argv)
{
    unsigned int height;
    unsigned int width;
    int i,j;
    printf("enter width: ");
    scanf("%d", &width);
    printf("enter height: ");
    scanf("%d", &height);
    int (*array)[width] = malloc (sizeof(int[height][width]));
    for (i = 0; i < height; i++ )
        for (j = 0; j < width; j++ )
            array[i][j] = i;
    for (i = 0; i < height; i++ ) {
        for (j = 0; j < width; j++ )
            printf("%d ", array[i][j]);
        printf("n");
    }
}

和控制台:

enter width: 10
enter height: 6
0 0 0 0 0 0 0 0 0 0 
1 1 1 1 1 1 1 1 1 1 
2 2 2 2 2 2 2 2 2 2 
3 3 3 3 3 3 3 3 3 3 
4 4 4 4 4 4 4 4 4 4 
5 5 5 5 5 5 5 5 5 5 

我承认这令人惊讶 - 我不知道这存在......


编辑 - 使用结构:

#include <stdio.h>
#include <stdlib.h>
typedef struct Cell {
    int data;
    // stuff to be added later
} Cell;
typedef struct Grid {
    unsigned width;
    unsigned height;
    Cell *cell;
} Grid;
Grid initGrid(unsigned width, unsigned height) {
    Grid g;
    g.width = width;
    g.height = height;
    g.cell = malloc( sizeof(Cell[height][width]) );
    return g;
}
int main(int argc, char ** argv)
{
    unsigned int height;
    unsigned int width;
    int i,j;
    Grid test;
    printf("enter width: ");
    scanf("%d", &width);
    printf("enter height: ");
    scanf("%d", &height);
    test = initGrid (width, height);
    Cell (*array)[width] = test.cell;
    for (i = 0; i < height; i++ )
        for (j = 0; j < width; j++ )
            array[i][j].data = i;
    for (i = 0; i < height; i++ ) {
        for (j = 0; j < width; j++ )
            printf("%d ", array[i][j].data);
        printf("n");
    }
}

控制台输出:

enter width: 20
enter height: 10
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 
2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 
3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 
4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 
5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 
6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 
7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 
8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 
9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 

有一个铸造警告,我没有时间解决,但可以实施这个想法 - 干净利落地做......再次,这是一个POC而不是一个实际的程序

你在这里几乎不走运,因为在 C 中没有办法在struct定义中使用可变数组长度。你可以做的是:

typedef struct Grid {
    unsigned width, height;
    void* cell_internal;    //Type: Cell(*)[height]
} Grid;
#define cell(myGrid) ((Cell(*)[(myGrid).height])(myGrid).cell_internal)
//in the constructor of Grid
newGrid->width = ...;
newGrid->height = ...;
cell(*newGrid) = malloc(newGrid->width*sizeof(*cell(*newGrid)));
for(unsigned x = 0; x < newGrid->width; x++) {
    for(unsigned y = 0; y < newGrid->height; y++) {
        cell(*newGrid)[x][y] = ...;
    }
}

这是一个肮脏的小技巧,但它应该可以正常工作。很酷的部分是,您可以简单地用cell(aGrid)[x][y]解决网格单元格。缺点是,它确实非常彻底地掩盖了实际发生的事情。而且没有多少人能真正读懂cell()宏的作用。(提示:它只是将void*强制转换为指向列数组Cell(*)[myGrid.height]的指针,无论该时间点的myGrid.height是什么。


当然,你可以像这样更明确:

typedef struct Grid {
    unsigned width, height;
    void* cell_internal;    //Type: Cell(*)[height]
} Grid;
//in the constructor of Grid
newGrid->width = ...;
newGrid->height = ...;
Cell (*cells)[newGrid->height] = malloc(newGrid->width*sizeof(*cells));
newGrid->cell_internal = cells;
for(unsigned x = 0; x < newGrid->width; x++) {
    for(unsigned y = 0; y < newGrid->height; y++) {
        cells[x][y] = ...;
    }
}

这种方法的缺点是,您需要为每个函数中的cell_internal指针显式创建一个别名指针

,该函数使用
Cell (*cells)[myGrid->height] = myGrid->cell_internal;

也许,这是更好的方法,因为它似乎可供更多人阅读。

使用灵活的数组。 使用两个malloc()调用是微不足道的,如果您愿意突破对齐限制或严格混叠的限制,或者想要编写代码来强制对齐用于存储Cell结构的malloc()部分,则可以只使用一个调用。

typedef struct Grid {
    unsigned width;
    unsigned height;
    Cell *cell[];
} Grid;
Grid *initGrid(unsigned width, unsigned height )
{
    // the Grid structure itself
    size_t bytesNeeded = sizeof( Grid );
    // space for pointers
    bytesNeeded += height * sizeof( Cell * );
    Grid *g = malloc( bytesNeeded );
    g->width = width;
    g->height = height;
    // get all the data needed with one malloc call
    g->cell[ 0 ] = malloc( width * height * sizeof( Cell ) );
    // fill in the pointers
    for ( unsigned ii = 1; ii < height; ii++ )
    {
        g->cell[ ii ] = g->cell[ 0 ] + ii * width;
    }
    return g;
}
void freeGrid( Grid *g )
{
    free( g->cell[ 0 ] );
    free( g );
}

如果你不介意突破严格混叠的极限,你可以使用一个灵活的数组和一次调用malloc()来完成(它留给读者强制对齐数据部分,这样就不会有潜在的对齐问题 - 这绝对是可能的(:

typedef struct Grid {
    unsigned width;
    unsigned height;
    Cell *cell[];
} Grid;
Grid *initGrid(unsigned width, unsigned height )
{
    // the Grid structure itself
    size_t bytesNeeded = sizeof( Grid );
    // space for pointers
    bytesNeeded += height * sizeof( Cell * );
    // space for data
    bytesNeeded += width * height * sizeof( Cell );
    Grid *g = malloc( bytesNeeded );
    g->width = width;
    g->height = height;
    // fill in the pointers
    // (technically a strict-aliasing/alignment violation as it assumes
    // that &(g->cell[ height ]) is suitable to store a Cell...)
    for ( unsigned ii = 0; ii < height; ii++ )
    {
        g->cell[ ii ] = ( Cell * ) &(g->cell[ height ]) +
            ii * width;
    }
    return g;
}

相关内容

最新更新