代码之家  ›  专栏  ›  技术社区  ›  Riccardo Orlando

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

  •  3
  • Riccardo Orlando  · 技术社区  · 6 年前

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

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

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

    这就是我目前的情况:

    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;
    

    Post-Post脚本:每当我使用 malloc 。我在这里做(或试图做)什么可怕的错误吗?

    4 回复  |  直到 6 年前
        1
  •  6
  •   unwind    6 年前

    你不能用双重索引( cell[x][y] )在C中,无法表示每行要跳转的字节数是动态的。

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

    简单地说:

    Cell *cell;
    

    struct (保存 width 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() 完成分配时的分配,以避免内存泄漏。严格地说,在现代系统上,当程序结束时,操作系统将释放所有资源,但无论如何释放都是一种好形式:

    void destroyGrid(Grid g)
    {
      free(g.cell);
    }
    
        2
  •  0
  •   cmaster - reinstate monica    6 年前

    在这里,您很不走运,因为在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;
    

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

        3
  •  0
  •   Andrew Henle    6 年前

    使用灵活的阵列。用两个就可以了 malloc() 调用,如果您希望提高对齐限制或严格别名的限制,或者希望编写代码来强制对齐 malloc() 'd用于存储 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;
    }
    
        4
  •  0
  •   Omer Dagan    6 年前

    继这一优秀职位之后: 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,不是一个实际的程序