我正在使用一个非常大的 5D 数组,我需要将其读入连续内存(另一个 5D 数组)。我无法将数组放在堆栈上,因为它太大并且会产生段错误。我所做的是使用 malloc 动态创建一个 5D 数组,但是我发现它不是连续的内存。是否有一个优雅的解决方案,或者无论如何它都会变得混乱?
6 回答
来自 Jens Gustedt:不要使用假矩阵。
分配一个维度为 A x B x C x D x E 的 5 维矩阵(在编译时不需要知道维度),如下所示:
float (*matrix5d)[B][C][D][E] = malloc(sizeof(float[A][B][C][D][E]));
只需调用一次 free 即可释放内存。
free(matrix5d);
请注意,对于可变长度数组,以上要求 C99 或更高版本。
通过连续的内存块表示是 C 数组的显着属性之一。多维数组是数组的数组,因此与任何其他数组一样是连续的,所以如果你想要一个真正的 5D 数组,那么你当然需要连续的内存。正如其他一些答案所观察到的,为确保获得连续的内存块,您必须一次分配整个内存。
尽管您可以形成由指向 [[指向 [指向...的指针的数组]] 数组的指针数组组成的数据结构,但它们根本不是一回事,就像指针不是数组一样。您可以将索引运算符 ,[]
用于此类数据结构,就像使用多维数组一样,但这并不能使它们成为同一件事。
@EvelynParenteau 建议使用 1D 数组模拟 5D 数组,这确实是满足您的连续性要求的一种方法。您甚至可以编写宏来使索引到这样的数组更容易。
但是只要你至少使用C99,你就可以动态分配一个真正的5D数组。一般形式可能如下所示:
void allocate_5d(unsigned dim1, unsigned dim2, unsigned dim3, unsigned dim4,
unsigned dim5, double (**aptr)[dim2][dim3][dim4][dim5]) {
*aptr = malloc(dim1 * sizeof(**aptr));
}
它将像这样使用:
void do_something(unsigned dim1, unsigned dim2, unsigned dim3, unsigned dim4,
unsigned dim5) {
double (*array)[dim2][dim3][dim4][dim5];
allocate_5d(dim1, dim2, dim4, dim4, dim5, &array);
if (!array) {
// Handle allocation failure ...
}
array[0][0][0][0][0] = 42;
// ...
free(array);
}
如果维度 2 - 5 是编译时常量,那么您甚至可以在 C90 中执行 this(略有不同),但上述变化取决于可变长度数组,这是 C99 中的新内容。
有一种方法可以使记忆连续,但无论是优雅还是凌乱,我都会留给你;)
首先,让我们考虑一维数组的情况。在这种情况下,获得连续内存是微不足道的。您从中获得的内存malloc
将是连续的。看起来很简单,但我们稍后将使用这个事实来获得一个 5 维连续数组。
M
现在,让我们考虑一个大小为的二维数组N
。这是一种创建方法(假设我们使用float
s)。
float** array2d = malloc(M * sizeof(float*));
for (int i = 0; i < M; i++) {
array2d[i] = malloc(N * sizeof(float));
}
严格来说,这不是二维数组,而是数组的数组。array2d
现在,我们可以访问likearray2d[0][0]
等元素array2d[0][1]
。从概念上讲,这非常好,但正如您所指出的,我们不一定有连续的内存,因为我们多次调用malloc
. 我们需要的是一种M*N
在一次调用中分配存储浮点数所需的所有内存的方法malloc
。
float* array2d = malloc(M * N * sizeof(float));
请注意,在这种形式中,array2d
isfloat*
而不是float**
,即它是一个浮点数组,而不是一个浮点数组数组。所以,我们不能再做array2d[0][0]
任何事情了。我们现在如何索引这个数组?
这完全取决于我们来决定如何在内存中布置这个二维数组。假设这M
是数组的“宽度”(表示一行中的元素数),这N
是数组的“高度”(表示数组中的行数)。另外,假设M
数组中的第一个条目是第一行,下一个M
条目是第二行,依此类推。所以要读取 row y
, column的条目x
,我们可以这样做:
float data = array2d[y * M + x];
假设我们想要元素 (0, 0)。然后y * M + x
简单地变成0,所以我们很好。现在假设我们想要元素 (1, 0)(即第二行中的第一个元素)。然后,y * M + x
变为M
,正如我们在上面所决定的,它是第二行的开始位置。
我们可以将这种方法推广到更高的维度。L
假设我们有一个大小为 byM
的三维数组N
。您可以将其视为在内存中L
按顺序排列的二维数组,大小均为M
. N
然后,要访问元素 ( x
, y
, z
),我们会这样做:
float data = array3d[z * (M * N) + y * (M) + x];
从概念上讲,您可以将其视为跳过前z
二维数组,然后跳过该y
数组的第一行,然后转到x
该行的第 th 个元素。对于更多的维度,索引时有更多的乘法项,但方法基本相同。
一种思考方式是使用malloc
分配 4d 数组的 1d 数组,因为从根本malloc
上只能分配 1d 数组,而 Nd 数组只是 (N-1)-d 数组的 1d 数组。
但是,就像任何由 分配的数组一样malloc
,“数组对象”实际上是一个指针,因此您不应该使用它sizeof()
来获取数组的大小。
#include <stdio.h>
#include <stdlib.h>
typedef int Array_4D_Type[4][3][2][1];
int main(void) {
Array_4D_Type *arr = malloc(5 * sizeof(Array_4D_Type));
// ^^^^^^^^^^^^^^^^ here, allocate a length-5 vector of 4d array type
int *p = &arr[0][0][0][0][0];
for (int i = 0 ; i < 120 ; i++){
p[i] = i;
}
printf("arr_start = %d, end = %d\n", arr[0][0][0][0][0], arr[4][3][2][1][0]);
return 0;
}
更新:
正如评论中提到的,在typedef
这里使用强制数组是静态大小的,除了顶部维度。
这里的使用typedef
只是为了使指向数组的语法更简洁一些。
但是,启用 VLA 后,它int (*arr)[n][o][p][q] = malloc(m*sizeof(*arr));
仍然可以工作,并允许您在每个维度上指定动态大小。
通过动态分配,使用 malloc:
int** x;
x = malloc(dimension1_max * sizeof(int*));
for (int i = 0; i < dimension1_max; i++) {
x[i] = malloc(dimension2_max * sizeof(int));
}
[...]
for (int i = 0; i < dimension1_max; i++) {
free(x[i]);
}
free(x);
这将分配一个大小为dimension1_max * dimension2_max 的二维数组。因此,例如,如果您想要一个 640*480 数组(图像的 fe 像素),请使用 dimension1_max = 640, dimension2_max = 480。然后您可以使用 x[d1][d2] 访问该数组,其中 d1 = 0.. 639,d2 = 0..479。
但是在 SO 或 Google 上的搜索也揭示了其他可能性,例如在这个 SO question
请注意,在这种情况下,您的数组不会分配连续的内存区域(640*480 字节),这可能会给假设这种情况的函数带来问题。因此,要使数组满足条件,请将上面的 malloc 块替换为:
int** x;
int* temp;
x = malloc(dimension1_max * sizeof(int*));
temp = malloc(dimension1_max * dimension2_max * sizeof(int));
for (int i = 0; i < dimension1_max; i++) {
x[i] = temp + (i * dimension2_max);
}
[...]
free(temp);
free(x);
以类似的方式,您可以动态构建 5d 数组
如果我理解您的问题,您有一个当前的 5D 数组,并且您需要为该数组分配存储空间并制作该数组的副本,然后您希望以顺序方式访问这些值。正如其他人所指出的,该方法是使用指向 4D 数组的指针来分配一块内存dim1 * sizeof 4D
来保存现有数组。(您可以考虑为组成 5D 数组的dim1 行分配)。然后,您可以简单地复制现有数组,(使用memcpy
等)然后将指针分配给第一个元素以进行顺序访问。
好处是您分配一个块来保存现有数组的副本。free
当您完成使用副本时,这将只需要一个。
这不适用于假(指向指针的指针......内存集合)
下面是一个简短的示例,它创建了一个dim1
指向构成现有数组的剩余 4d(在单个块分配中)的指针,其中除了您的dim1
尺寸之外的所有尺寸在编译时都是已知的。现有的 5D 数组a
被复制到分配给 的新内存块中b
。然后将整数指针“p”分配给 的起始地址b
。的值b
通过指针顺序访问p
。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main (void) {
int a[2][2][2][2][2] = { { { {{1,2}, {3,4}}, /* 5D Array */
{{5,6}, {7,8}} },
{ {{1,2}, {3,4}},
{{5,6}, {7,8}} } },
{ { {{1,2}, {3,4}},
{{5,6}, {7,8}} },
{ {{1,2}, {3,4}},
{{5,6}, {7,8}} } } };
/* ptr to 5D, ptr to int* */
int (*b)[2][2][2][2] = NULL, *p = NULL;
/* allocate block to hold a */
b = malloc (sizeof a/sizeof *a * sizeof *b);
memcpy (b, a, sizeof a/sizeof *a * sizeof *b); /* copy a to b */
p = ****b; /* assign address of first element */
printf ("\nb:\n"); /* ouput using sequential access */
for (int i = 0; i < (int)(sizeof a/sizeof *****a); i++)
printf (" *(p + %2d) : %d\n", i, p[i]);
free (b); /* single free is all that is required */
return 0;
}
示例使用/输出
$ ./bin/arr5dstatic1
b:
*(p + 0) : 1
*(p + 1) : 2
*(p + 2) : 3
*(p + 3) : 4
*(p + 4) : 5
*(p + 5) : 6
*(p + 6) : 7
*(p + 7) : 8
*(p + 8) : 1
*(p + 9) : 2
*(p + 10) : 3
*(p + 11) : 4
*(p + 12) : 5
*(p + 13) : 6
*(p + 14) : 7
*(p + 15) : 8
*(p + 16) : 1
*(p + 17) : 2
*(p + 18) : 3
*(p + 19) : 4
*(p + 20) : 5
*(p + 21) : 6
*(p + 22) : 7
*(p + 23) : 8
*(p + 24) : 1
*(p + 25) : 2
*(p + 26) : 3
*(p + 27) : 4
*(p + 28) : 5
*(p + 29) : 6
*(p + 30) : 7
*(p + 31) : 8
其余的评论和答案建议您找到使用 5D 阵列设置以外的其他方法,这是有充分理由的。如果您可以修改生成原始 5D 数组中捕获的数据的任何内容,以便以其他格式输出数据,则值得调查一下。