如何在C中的结构内初始化动态2D数组?

时间:2019-02-15 13:03:59

标签: c arrays struct

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

    struct mystruct {
        int **my2darray;

    }

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

我尝试这样做:

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

它工作正常。 现在我的问题是为什么会这样?

3 个答案:

答案 0 :(得分:1)

上面的代码从不设置rowscolumns,因此代码具有undefined behavior来读取这些值。

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

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

答案 1 :(得分:1)

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

首先,请检查Correctly allocating multi-dimensional arrays。您将需要一个2D数组,而不是一些查找表。

要分配这样一个真正的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 )

读取为:如果arr2d是类型array2d_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; 
}

关于指针到指针的优势:

  • 可以通过单个函数调用分配/释放的实际2D数组,并且可以将其传递给诸如memcpy之类的函数。

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

  • 结构中没有多余的杂物,只是数组。
  • 由于内存在整个堆中被分段,因此没有高速缓存会在访问数组时丢失。

答案 2 :(得分:0)

在您提供的代码示例中,变量rowscolumns在使用前尚未初始化,因此它们可以包含任何内容,但很可能等于0。结果永远是不可预测的。

在C中需要 2D 数组时,封装内存分配,将内存释放为函数以简化任务和提高可读性很有用。例如,在您的代码中,以下行将创建一个由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

int数组中通常被称为的东西,也允许通过索引访问每个元素。实际上(即使通常称为数组。)它不是数组。该变量中元素的位置存储在内存中的一个连续位置。

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

但是C会保留这些位置,以便它们都可以作为2D数组索引。