在结构中需要一个“变量”大小的二维数组

时间:2018-03-15 12:55:56

标签: c arrays multidimensional-array struct dynamic-memory-allocation

我正试图实现一个细胞网格,类似于康威的生命游戏。

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

这类似于数组的大小,但初始化后的数组具有固定大小。

这是我到目前为止所做的:

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数据类型?

Post scriptum:作为C新手,我认为以下内容可行:

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

post post scriptum:每当我使用malloc时,我总是感到不安。我在做什么(或试图做)这里有什么可怕的错误?

4 个答案:

答案 0 :(得分:4)

你不能用C中的双索引(cell[x][y])来做,没有办法表明每行跳转的字节数是动态的。

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

说清楚:

Cell *cell;
<{1>}中的

(保持structwidth),然后像这样索引:

height

编译器不一定会内联这个,而且它会非常紧张。可能比使用更多内存和另一层间接的锯齿状阵列更快。

分配很简单:

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()

答案 1 :(得分:0)

这里几乎没有运气,因为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;

可能这是更好的方法,因为它似乎对更多人来说是可读的。

答案 2 :(得分:0)

使用灵活的阵列。使用两个malloc()调用很简单,如果你想要限制对齐限制或严格别名或想要编写代码来强制对齐部分,那么可能只做一个malloc()用于存储Cell结构。

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;
}

答案 3 :(得分:0)

在这篇优秀的帖子之后: How do I work with dynamic multi-dimensional arrays in C? 阅读@JensGustedt帖子并关注他的链接variable length arrays (VLAs)

实际上有一种方法 - 我跟着他的帖子写了一个小测试程序来验证:

#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而不是实际的程序