繁体   English   中英

如何在c中的结构内初始化动态二维数组?

[英]How to initiliaze a dynamic 2D array inside a struct in c?

我想使用一个结构来包含一些数据并在我的程序中的不同函数之间传递它们,这个结构必须包含一个动态二维数组(我需要一个矩阵),维度根据程序参数而变化。 所以这是我的结构:

    struct mystruct {
        int **my2darray;

    }

我有一个从文件中读取数字的函数,并且必须将每个数字分配给结构数组的一个单元格。

我试过这样做:

    FILE *fp = fopen(filename, "r");
    int rows;
    int columns;
    struct mystruct *result = malloc(sizeof(struct mystruct));
    result->my2darray = malloc(sizeof(int)*rows); 
    int tmp[rows][columns];
    for(int i = 0;i<rows;i++) {
        for(int j = 0;j<columns;j++) {
            fscanf(fp, "%d", &tmp[i][j]); 
        }
        result->my2darray[i]=malloc(sizeof(int)*columns);
        memcpy(result->my2darray[i],tmp[i],sizeof(tmp[i]));
    }

但这给了我一个奇怪的结果:除了第一行之外,所有行都被正确存储。 (我确定问题不在于文件扫描)。 如果我更改第四行代码:

    result->my2darray = malloc(sizeof(int)*(rows+1)); 

它工作正常。 现在我的问题是为什么会发生这种情况?

这是使用该语言的一些“新”功能的答案:灵活的数组成员和指向 VLA 的指针。

首先,请检查正确分配多维数组 你需要一个二维数组,而不是一些查找表。

要分配这样一个真正的 2D 数组,您可以使用灵活的数组成员:

typedef struct
{
  size_t x;
  size_t y;
  int flex[];
} array2d_t;

它将被分配为一个真正的数组,尽管“错位”为一个维度:

size_t x = 2;
size_t y = 3;
array2d_t* arr2d = malloc( sizeof *arr2d + sizeof(int[x][y]) );

因为灵活数组成员的问题是它们既不能是 VLA 也不能是二维的。 尽管将其转换为另一种整数数组类型是安全的(在别名和对齐方面),但语法非常邪恶:

int(*ptr)[y] = (int(*)[y]) arr2d->flex;  // bleh!

将所有这些邪恶的语法隐藏在宏后面是可能的:

#define get_array(arr2d) \
  _Generic( (arr2d),     \
            array2d_t*: (int(*)[(arr2d)->y])(arr2d)->flex )

读作:如果arr2darray2d_t*类型,则访问该指针以获取 flex 成员,然后将其转换为适当类型的数组指针。

完整示例:

#include <stdlib.h>
#include <stdio.h>

typedef struct
{
  size_t x;
  size_t y;
  int flex[];
} array2d_t;

#define get_array(arr2d) \
  _Generic( (arr2d),     \
            array2d_t*: (int(*)[(arr2d)->y])(arr2d)->flex )

int main (void)
{
  size_t x = 2;
  size_t y = 3;

  array2d_t* arr = malloc( sizeof *arr + sizeof(int[x][y]) );
  arr->x = x;
  arr->y = y;
  

  for(size_t i=0; i<arr->x; i++)
  {
    for(size_t j=0; j<arr->y; j++)
    {
      get_array(arr)[i][j] = i+j;
      printf("%d ", get_array(arr)[i][j]);
    }
    printf("\n");
  }

  free(arr);
  return 0; 
}

指针对指针的优点:

  • 一个实际的二维数组,可以通过单个函数调用分配/释放,并且可以传递给像 memcpy 这样的函数。

    例如,如果您有两个array2d_t*指向分配的内存,则可以使用单个 memcpy 调用复制所有内容,而无需访问单个成员。

  • 结构中没有额外的混乱,只有数组。

  • 由于内存在整个堆中被分段,因此在访问数组时没有缓存未命中。

上面的代码从不设置rowscolumns ,因此代码在读取这些值时具有未定义的行为

假设您正确设置了这些值,这不会分配适当的内存量:

result->my2darray = malloc(sizeof(int)*rows);

您实际上是为int数组而不是int *数组分配空间。 如果后者更大(并且很可能是),那么您还没有为数组分配足够的空间,并且您再次通过写入超过分配内存的末尾来调用未定义的行为。

您可以像这样分配适当的空间量:

result->my2darray = malloc(sizeof(int *)*rows);

甚至更好,因为这不取决于实际类型:

result->my2darray = malloc(sizeof(*result->my2darray)*rows);

此外,无需创建临时数组来读取值。 只需将它们直接读入my2darray

for(int i = 0;i<rows;i++) {
    result->my2darray[i]=malloc(sizeof(int)*columns);
    for(int j = 0;j<columns;j++) {
        fscanf(fp, "%d", &result->my2darray[i][j]); 
    }
}

在您提供的代码示例中,变量rowscolumns在使用前尚未初始化,因此它们可以包含任何内容,但可能等于 0。无论哪种方式,如所写,结果将始终是不可预测的。

当在 C 中需要二维数组时,将内存分配和释放内存封装到函数中以简化任务并提高可读性很有用。 例如,在您的代码中,以下行将创建一个包含 5 个指针的数组,每个指针指向 20 个int存储位置:(创建 100 个索引可寻址的int位置。)

int main(void)
{
    struct mystruct result = {0}; 

    result.my2darray = Create2D(5, 20);

    if(result.my2darray)
    {
        // use result.my2darray 
        result.my2darray[0][3] = 20;// for simple example, but more likely in a read loop                         
        // then free result.my2darray
        free2D(result.my2darray, 5);
    }
    return 0;
}

使用以下两个函数:

int ** Create2D(int c, int r)
{   
    int **arr;
    int    y;

    arr   = calloc(c, sizeof(int *)); //create c pointers (columns)
    for(y=0;y<c;y++)
    {
        arr[y] = calloc(r, sizeof(int)); //create r int locations for each pointer (rows)
    }
    return arr;
}

void free2D(int **arr, int c)
{
    int i;
    if(!arr) return;
    for(i=0;i<c;i++)
    {
        if(arr[i]) 
        {
            free(arr[i]);
            arr[i] = NULL;
        }
    }
    free(arr);
    arr = NULL;
}

请记住,您使用此技术创建的实际上是 5 个不同的指针位置,每个位置都指向一组 20 个int位置。 这有助于像索引一样使用数组,即我们可以说result.my2darray[1][3]表示 5X20 数组的第二列第四元素,当它根本不是真正的数组时。

int some_array[5][20] = {0};//init all elements to zero

就是通常被称为C中的int阵列,还允许通过索引访问每个元素。 实际上(尽管通常称为数组。)它不是数组。 此变量中元素的位置存储在内存中的一个连续位置。

|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0...  (~ 82 more)

但是 C 维护这些位置,以便它们都可以作为 2D 数组进行索引。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM