【问题标题】:Saving multidimensional arrays in C在 C 中保存多维数组
【发布时间】:2012-10-25 11:36:49
【问题描述】:

我之前使用 .mat 文件在 Matlab 中保存了多维数组(例如,大小为 100x100x100 的数组 A),效果非常好。

在 C 中保存此类多维数组的最佳方法是什么?我能想到的唯一方法是将其存储为 2D 数组(例如,将 KxNxM 数组转换为 KNxM 数组)并小心记住它是如何保存的。

还希望以稍后可以在 Matlab 中打开以进行后处理/绘图的方式保存它。

【问题讨论】:

  • 首先我相信你需要思考为什么你需要将它保存为一个多数组开始。这是原始内存图像吗?是二进制数据吗?一堆整数?浮动?
  • 例如,如果我可以将数据加载到另一个程序中会很好,用 C 或 Matlab 编写。我想让这个过程或在另一个程序中打开数据尽可能简单。

标签: c arrays matlab multidimensional-array save


【解决方案1】:

C 可以很好地处理多维数组 (double array[K][M][N];),它们与一维数组一样连续存储在内存中。事实上,写double* onedim = &array[0][0][0]; 然后使用与 3-D 和 1-D 数组完全相同的内存区域是合法的。

要将其从 C 语言导入 matlab,您可以在 C 语言中使用 fwrite(array, sizeof array[0][0][0], K*M*N*, fptr),在 MatLab 中使用 array = fread(fileID, inf, 'real*8')。您可能会发现reshape 函数很有帮助。

【讨论】:

  • 是的,我已经考虑过了,必须小心索引和所有内容,是否有更简单且不易出错的出路...特别是如果,比如说,我想要将数据加载到另一个 C 程序中,在这种情况下,编写我自己的版本 reshape 也可能会有所帮助。
  • @dblazevski 实际上,您不需要自己的reshape 版本。在 C(多暗淡)数组中,数据是按顺序存储的,因此,您实际上需要读/写的是偏移一个指针(当然,前提是您不想要任何转置)。
  • @Ben Voigt 您在编辑后的帖子中提出的建议似乎很有希望,但我仍然对部分内容感到困惑。例如,当您编写double* onedim = &array[0][0][0] 时,我不确定它到底定义了什么,尤其是[0][0][0] 部分,因为我不确定您定义的是什么大小的数组,如果甚至定义了大小。我也不确定如何修改您定义的onedim 的条目。
  • @dblazevski:添加了声明。您知道[] 下标括号与指针和数组一样有效,对吧? (其实数组在和[]一起使用的时候会转换成指针)
  • 哦,我明白了,您假设array 是之前定义的变量的名称.. 没听懂。酷,我明天试试(我住的地方差不多晚上 9 点......)。看来我所要做的就是将onedim 的内容写入一个文件并在Matlab 中打开它,正如你所说,希望在使用reshape 之后我得到我想要的。与我最初的计划相比,这似乎很有希望,而且可能不太容易出错。
【解决方案2】:

C 可以很好地处理 3D 数组:

double data[D0][D1][D2];
...
data[i][j][k] = ...;

尽管对于非常大的数组(例如您的示例),您可能希望动态分配数组,而不是像上面那样将它们声明为 auto 变量,因为 auto 变量的空间(通常 em> 堆栈,但并非总是如此)可能非常有限。

假设您的所有维度在编译时都是已知的,您可以执行以下操作:

#include <stdlib.h>
...
#define DO 100
#define D1 100
#define D2 100
...
double (*data)[D1][D2] = malloc(sizeof *data * D0);
if (data)
{
  ...
  data[i][j][k] = ...;
  ...
  free(data);
}

这将从堆中分配一个 D0xD1xD2 数组,您可以像访问任何常规 3D 数组一样访问它。

如果直到运行时才知道您的尺寸,但您正在使用支持可变长度数组的 C99 编译器或 C2011 编译器,您可以执行以下操作:

#include <stdlib.h>
...
size_t d0, d1, d2;
d0 = ...;
d1 = ...;
d2 = ...;
...
double (*data)[d1][d2] = malloc(sizeof *data * d0);
if (data)
{
  // same as above
}

如果您的尺寸直到运行时才知道,并且您使用的编译器支持可变长度数组(C89 或更早版本,或不支持 VLA 的 C2011 编译器),您'将需要采取不同的方法。

如果需要连续分配内存,则需要执行以下操作:

size_t d0, d1, d2;
d0 = ...;
d1 = ...;
d2 = ...;
...
double *data = malloc(sizeof *data * d0 * d1 * d2);
if (data)
{
  ...
  data[i * d0 * d1 + j * d1 + k] = ...;
  ...
  free(data);
}

请注意,您必须将 ijk 索引映射到单个索引值。

如果内存不需要是连续的,你可以像这样进行分段分配:

double ***data;
...
data = malloc(d0 * sizeof *data);
if (data)
{
  size_t i;
  for (i = 0; i < d0; i++)
  {
    data[i] = malloc(d1 * sizeof *data[i]);
    if (data[i])
    {
      size_t j;
      for (j = 0; j < d1; j++)
      {
        data[i][j] = malloc(d2 * sizeof *data[i][j]);
        if (data[i][j])
        {
          size_t k;
          for (k = 0; k < d2; k++)
          {
            data[i][j][k] = initial_value();
          }
        }
      }
    }
  }
}

并将其释放为

for (i = 0; i < d0; i++)
{
  for (j = 0; j < d1; j++)
  {
    free(data[i][j]);
  }
  free(data[i]);
}
free(data);

这不是推荐的做法,顺便说一句;即使它允许您索引data,就好像它是一个 3D 数组,但要权衡的是更复杂的代码,尤其是如果malloc 在分配循环中途失败(那么您必须退出到目前为止您所做的所有分配)。它也可能会导致性能损失,因为不能保证内存被很好地定位。

编辑

至于将这些数据保存在文件中,这取决于您需要做什么。

最便携的是将数据保存为格式化文本,如:

#include <stdio.h>
FILE *dat = fopen("myfile.dat", "w"); // opens new file for writing
if (dat)
{
  for (i = 0; i < D0; i++)
  {
    for (j = 0; j < D1; j++)
    {
      for (k = 0; k < D2; k++)
      {
        fprintf(dat, "%f ", data[i][j][k]);
      }
      fprintf(dat, "\n");
    }
    fprintf(dat, "\n");
  }
}

这会将数据作为浮点数序列写出,每行末尾有一个换行符,每个“页面”末尾有两个换行符。读回数据基本上是相反的:

FILE *dat = fopen("myfile.dat", "r"); // opens file for reading
if (dat)
{
  for (i = 0; i < D0; i++)
    for (j = 0; j < D1; j++)
      for (k = 0; k < D2; k++)
        fscanf(dat, "%f", &data[i][j][k]);
}

请注意,这两个 sn-ps 都假定数组有一个已知的、固定的大小,不会随着运行而改变。如果不是这种情况,您显然必须在文件中存储其他数据以确定数组需要多大。也没有类似于错误处理的东西。

我要留下很多 的东西,因为我不确定你的目标是什么。

【讨论】:

  • 确实是全貌。 +1
  • 很高兴知道这一切。我的主要兴趣*是以一种最容易在另一个程序中使用的方式将数据保存到文件中。这对于了解如何在数组很大时更好地编程非常有用。 *抱歉,我原本应该更准确地说“保存”时使用“商店”可能会混淆人们。
【解决方案3】:

c 可以处理 3 维数组,那么为什么不使用它呢?

将其写入.mat 文件需要一些工作,但似乎并不太难。

.mat 格式描述为here

【讨论】:

    【解决方案4】:

    三重指针:

    double*** X;
    X= (double***)malloc(k*sizeof(double**));
    for(int i=0; i<k;i++)
    {
       X[i]=(double**)malloc(n*sizeof(double*));
       for(int j=0; j<n;j++)
       {
           X[i][j]=(double*)malloc(m*sizeof(double));
       }
    }
    

    这样访问每个值的方法非常直观:X[i][j][k]。
    如果你愿意,你可以使用一个唯一的数组:

    double* X;
    X=(double*)malloc(n*m*k*sizeof(double));
    

    您可以通过这种方式访问​​每个元素:

    X[i*n*m+j*n+k]=0.0;
    

    如果你使用三重指针,别忘了释放内存。

    【讨论】:

    • 出于对混淆的热爱,不!不要这样做。了解数组指针和correct ways动态分配多维数组。
    • @Lundin 我认为你大错特错:没有理由,因为这种方法是“正确的”。唯一的问题是维度 2 和 3 的块是静态的,统计上你对堆栈的访问速度更快,但用户可能希望像在 MATLAB 中那样重塑矩阵,而且我提到的方法也是正确的。
    • 你甚至没有分配一个矩阵!您正在分配一个指向指针的指针数组。内存不是相邻分配的,它将遍布堆。您不能将这样的潜在矩阵与 memcpy、bsort、qsort 等函数和其他适合矩阵处理的函数一起使用。除了我已经给你的链接之外,C FAQ 也是很好的阅读。现在你应该做的是声明一个数组指针数组,指向数组指针数组,指向数组。使用 typedef,以提高可读性。
    • 那为什么不加倍 X[k][n][m] 呢?因为你不能调整矩阵的大小。所以动态解决方案使它可以调整大小,就像在 MATLAB 中一样。但是很好,但不是完全动态的。
    【解决方案5】:

    当然,您也可以将其存储为 C 中的 3D 数组。不知道为什么你觉得你必须转换为 2D:

    double data[100][100][100];
    

    这当然需要相当多的内存(假设是 64 位 double 大约需要 7.6 MB),但例如在 PC 上应该没问题。

    不过,您可能希望避免将此类变量放入堆栈。

    【讨论】:

    • 正如您正确指出的那样,这不应该在堆栈上分配,所以这个答案并不是很有效。在大多数平台上,您必须在堆上分配如此大量的数据,see this
    • 内存不是大问题,我有大约 64 GB 的 RAM。问题是我想将数组保存到一个文件中,以便在另一个 C 或 Matlab 程序中使用它
    • @dblazevski:无论你有多少 RAM,堆栈内存仍然是个问题。
    • 好吧,我只是对堆栈内存有点熟悉(我做数值模拟,本身并不完全是计算机科学家)并且认为很多 RAM 可以解决堆栈问题。那么,假设我想以 3D 格式保存在 100x100x100 网格上定义的标量函数的值,有什么好的方法呢?
    • @dblazevski: RAM != stack != heap != virtual memory != 这个列表还在继续。除非您在编译/链接程序时请求更多,否则每个线程都被赋予有限的堆栈空间。您请求的越多,每个线程占用的内存就越多(无论是否使用)。在 FORTRAN 中,这在巨大的自动数组中最为明显。堆通常可以扩展以填充所有可用的虚拟内存,因此对于较大的数据类型,最好将它们保留在堆上(即使只是在本地范围内)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-25
    • 1970-01-01
    • 2015-01-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多