堆分配一个二维数组(非指针数组)

28
我正在编写C代码,希望能够堆分配512*256字节的内存。为了方便,我希望能够使用array [a] [b]的语法来访问数组元素,而无需计算正确的索引。我在网上看到的每篇教程都告诉我要创建一个指针数组,指向我想要在数组中使用的行数组。这意味着每个子数组都需要单独进行malloc和free。我想找到一种只需要调用一次malloc和一次free的解决方案。(因此所有元素都是连续的)我认为这是可能的,因为我不会构建一个锯齿形数组。
如果有人愿意分享声明这样的数组的语法,我将不胜感激。

1
C++是否可选?您可以创建一个简单的C++对象,重载索引运算符。 - Richard J. Ross III
4
这真的是对“我如何用C语言做这个”的一个有效回答吗?(意译:作者对回答方式有所质疑,但也承认作者已经在评论中提供了这种回答)我宁愿选择C而不是C++。谢谢。 - Ed S.
@EdS。虽然您可能更喜欢C语言,但在许多情况下,C++更好,而这将是其中之一。我只是将其留作评论,以确定它是否是一个选项。 - Richard J. Ross III
2
@RichardJ.RossIII:C++更适合分配二维数组? 呃? 我不这么认为,这在C中很容易实现,不需要C ++运算符重载。 - Ed S.
看看我的答案,与其他人不同,它实际上做了你要求的事情。 - R.. GitHub STOP HELPING ICE
7个回答

45

如果你想分配某种类型的数组,你需要将它分配给该类型的指针。

由于2D数组是数组的数组(在您的情况下,是512个256个字符的数组),因此您应该将其分配给指向256个字符的数组的指针:

char (*arr)[256]=malloc(512*256);
//Now, you can, for example:
arr[500][200]=75;

(将 *arr 周围的圆括号添加是为了将其转换为指向数组的指针,而不是指针数组。)


+1,不知怎么的,我在写答案的时候错过了你发的这个。 :-) - R.. GitHub STOP HELPING ICE
5
需要注意的是,自C99版本以后,维度不再需要在编译时知道。你可以从标准输入读取nm的值,并声明一个char arr[n][m]数组,或者在这种情况下使用char (*arr)[n] - Kos

15

假设你不需要与古老的C89标准兼容(目前只有MSVC和一些嵌入式目标编译器是如此过时的)。以下是如何实现:

int (*array)[cols] = malloc(rows * sizeof *array);

对于任何 a[0, rows)b[0, cols) 的值,array[a][b] 是有效的。

在C标准语言中,array 具有可变修改类型。如果您想把指针传递给其他函数,则需要在函数参数列表中重复这种类型,并确保至少传递了列数作为函数的一部分(因为它是可变修改类型的一部分)。

编辑: 我错过了 OP 只关心固定大小为 512x256 的事实。在这种情况下,C89 就足够了,你只需要:

int (*array)[256] = malloc(512 * sizeof *array);

如果你需要在函数之间传递指针,那么完全相同的类型可以在函数参数列表中使用(也可以作为函数返回类型,但是对于这种情况,你可能需要使用typedef... :-))


你在一般情况下是正确的,但是OP只想要512*256的数组。 - asaelr
实际上,这个答案非常好。虽然我现在只需要512*256,但我很容易看到这个需求在未来会发生变化。 - Paul
我喜欢通用案例方法,但是有一件事情我还没有理解。它是否仍然需要循环遍历外部数组以释放所有内部指针?还是只需要一个free()调用? - sacheie
@sacheie:对于每个malloc,您只能调用一次free;这是绝对正确的。array不是指针数组,而是数组的数组。每个array[i]衰减为指向其第一个元素的指针,但array[i]一个数组。 - R.. GitHub STOP HELPING ICE

14

如果您像这样分配数组,则需要两次调用free,但它允许使用array[a][b]样式语法,并且是连续的。

char **array = malloc(512 * sizeof(char *));
array[0] = malloc(512*256);
for (int i = 1; i < 512; i++)
    array[i] = array[0] + (256 * i);

更多信息请查看这里的array2: http://c-faq.com/aryptr/dynmuldimary.html


3
你可以将数据块放在 dope 向量之后,以组合这两个分配。 需要进行一定程度的繁琐类型转换,但并不难。 但是,如所示的代码存在严重的错误:你为 512 个 char 分配了空间,然后将其视为足够容纳 512 个 char * 的空间。 这几乎肯定会超出分配的末尾并导致崩溃。 - zwol
4
这不是一个二维数组,而是一个指针数组。它有一些优点(您可以在 O(1) 的时间复杂度内对行进行置换,而不是 O(cols)),也有一些缺点(每次访问都更加昂贵,因为需要经过额外的间接级别;需要更多的内存等)。无论如何,如果您采用这种方法,请考虑使用 Zack 建议的单个 malloc。这简化了错误处理(无需清除部分失败的情况),并确保内存局部性。 - R.. GitHub STOP HELPING ICE

5

由于您事先知道数组的大小,因此可以创建一个包含521x256数组的struct类型,然后动态分配struct


4

可以动态分配与多维数组相同类型的数组。

static char x[512][256];

这个东西是给你的,但由于类型衰变的原因它有点棘手。我只知道如何通过 typedef 来实现:

typedef char row[512];
row *x = malloc(sizeof(row) * 256);

这只能让您在运行时确定第二个维度的大小。如果两个维度都可以在运行时变化,那么您需要一个dope向量。


这个也错过了。请注意,除非您受限于C89,否则两个维度都可以变化;您只需要使用可变修改的指针类型即可。 - R.. GitHub STOP HELPING ICE

2

如果你知道数组的大小,你可以使用 typedef 来定义它,并创建一个指向它的指针。下面是一个简短的示例,演示了这种用法:

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

typedef int array2d[20][20];

int main() {
    int i,j;
    array2d *a = malloc(sizeof(array2d));
    for(i=0;i!=20;i++)
        for(j=0;j!=20;j++)
            (*a)[i][j] = i + j;

    for(i=0;i!=20;i++)
        for(j=0;j!=20;j++)
            printf("%d ",(*a)[i][j]);
    free(a);
    return 0;
}

有趣的解决方案,你能想出如何使用动态数组来实现吗? - Richard J. Ross III
@RichardJ.RossIII 很不幸,这对于动态大小的数组是不起作用的,因为typedef需要两个维度的编译时常量。你能做的最好的事情就是使一个维度固定而另一个维度是动态的,但它并不能完全实现动态化。 - Sergey Kalinichenko
1
typedef是不必要的,实际上在typedef中放置两个维度是有害的,因为你会被困在使用(*a)而不是简单的a的地方。如果你只放置列数,它会工作得更好。当然,在C99中,你可以使类型可变修改;我相信只要typedef具有块范围而不是文件范围,这甚至在typedef中也是有效的。 - R.. GitHub STOP HELPING ICE

0
所有的回答都很好。我只想为像我这样喜欢使用旧编译器(如Turbo C)和旧机器进行16位“复古”编程的老怪物添加一点内容。可变长度数组很棒,但并非必需。
    char (*array)[81];
    int lineCount;

    /* Go get your lineCount.*/
    lineCount = GetFileLines("text.fil");

    array = malloc(lineCount * 81);

这就是我们在早期实现“VLA”的方式。它的工作原理与现在完全相同。

    char (*array)[81] = malloc(lineCount * 81);  /* error pre C99 */

没有 VLA 的奢侈。

只是我陈旧而发黑的 2 分钱。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接