分配一个6xNxN的数组。

3

我有一个变量N。 我需要一个6xNxN的数组。

类似这样:

int arr[6][N][N];

但是,显然这样行不通。

我不确定如何进行分配,以便在N为5时可以访问例如arr[5][4][4],如果N为24,则访问arr[5][23][23]

请注意,N永远不会改变,因此我永远不需要重新分配arr

我该怎么办?int ***arr = malloc(6 * N * N * sizeof(int));能用吗?


1
在C99及以上版本中, int arr [6] [N] [N];实际上是有效的,但它也使用了大量堆栈空间,并且存在堆栈溢出的风险,因此最好使用malloc分配数组。(int ***arr = ...是错误的。) - user2357112
int ***arr = malloc(6 * N * N * sizeof(int)); 这样是不行的。 - Ajay Brahmakshatriya
如果 int ***arr = ... 是错误的,那正确的是什么? - No one
理想情况下,您应该只在 N 上放置 extern,并在实际需要的地方声明数组。 - bit_cracker007
3
@Noone:如果类型intsize_t小,那么6 * N * N * sizeof(int)可能会出现溢出的问题,在当前的64位体系结构上这种情况非常普遍。我曾经由于这种类型的问题而受挫。例如:int N = 30000;应该分配大约21亿字节的空间,这可能适合内存,但是6 * N * N超过了32位的INT_MAX,在转换为size_t之前最多只能包裹,并且malloc的值比预期的要小得多。为避免此类问题,请始终将sizeof()运算符放在第一位。 - chqrlie
显示剩余11条评论
4个回答

3

您可以将三维数组分配到堆上,如下所示:

int (*arr)[N][N] = malloc(sizeof(int[6][N][N]));

使用完毕后,您可以自由地进行操作。

free(arr);

另一种与@StoryTeller建议相同的写法是 -

int (*arr)[N][N] = malloc(6u * sizeof(*arr));

但是在这里,您需要注意在6后面加上u以防止有符号算术溢出。

此外,在像@chqrlie建议的那样size_tint宽度小的平台上仍可能存在问题,但在“大多数”常用平台上不会出现这种情况,因此您可以放心使用它。


2
我点赞了,但我认为malloc(6u * sizeof *arr);会更符合惯用语。尽管这种方法展示了“数组类型”的好处。 - StoryTeller - Unslander Monica
@StoryTeller:malloc(6 * N * N * sizeof(int)) 的真正问题在于在转换为 size_t 之前,6 * N * N 可能会导致溢出。更通用和更安全的替代方法是:int (*arr)[N][N] = malloc(sizeof(*arr) * 6); - chqrlie
@chqrlie - 可能会更好,但需要有意识地努力打破某些习惯。 - StoryTeller - Unslander Monica
@StoryTeller:在6 * sizeof(*arr)中,唯一可能发生有符号溢出的情况是当size_tint小不超过3位时。我不确定这是否符合标准。 - chqrlie
@AjayBrahmakshatriya - 一定要注意Charlie的评论。他们非常明智。 - StoryTeller - Unslander Monica
显示剩余11条评论

2

int arr[6][N][N];可以正常工作。你只需要将编译器和C语言知识更新到1999年或之后,因为变长数组(VLA)在该语言中被引入。

(如果你使用的是GCC 5.0以下版本,你必须明确告诉它不要使用古老的C标准,通过传递-std=c99-std=c11参数来实现。)

另外,如果你需要堆分配,可以这样做:

int (*arrptr)[Y][Z] = malloc( sizeof(int[X][Y][Z]) );

由于 int*** 不能指向 3D 数组,因此您无法执行 int ***arr = malloc(6 * N * N * sizeof(int));。一般来说,超过两个间接级别是您的程序设计完全有缺陷的明确标志。

有关详细信息,请参见:正确分配多维数组


请注意,可变长度数组(VLAs)仅在C11中是可选的。当然,它们无法在文件范围内工作,就像OP尝试的那样(请参见评论)。避免使用它们的另一个原因是可能会发生堆栈溢出,如果您无法知道大小。但当然,我同意关于三星程序员问题的观点。我会选择使用平坦分配的数组并编写索引表达式。 - user2371524
在实践中,没有人会使用支持C11但不支持VLA的编译器,因为VLAs现在是惯用的C。这不仅仅是因为实际的VLAs,而是因为它们允许具有可变长度的数组指针和函数参数数组。不支持这一点的编译器对于现代C编程来说是无用的。我们不需要考虑可移植性到无用的编译器。 - Lundin
这是一个非常强烈的观点,但我不赞同。惯用的 C 仍然传递指针和 size_t 参数。VLA 受到了很多批评,例如因为你无法从 OOM 条件中恢复。这篇 Usenet 帖子(当然不是权威的)有一个很好的列表。 - user2371524
现代化的、惯用的 C 语言示例是:void func (size_t n, int array[n]),而不是 void func(int* array, size_t n),后者是老式样式。现代化的惯用 C 语言是 int (*arr)[y] = malloc( sizeof(int[x][y] );,而不是 int* arr = malloc( x*y*sizeof(int) );,后者是 C90 样式的混乱数组。等等。现代版本要求可以使用指向 VLA 类型的指针。不使用它们就意味着回到了上世纪 90 年代的 C 编程风格。 - Lundin
我不这么认为。有理由使VLA成为可选项,而使用VLA真正能够产生更好的代码的情况非常少。你至少应该意识到你正在使用一个可选功能,并对此做出有意识的决定。 - user2371524
@FelixPalmen我真的看不出有任何理由。相反,唯一的原因似乎是出于某些政治原因,比如微软游说要求这样做。MS太懒了,不想实现C99,现在18年过去了还是不想做。 - Lundin

1
你想要的方法不能直接实现。对于多维数组的索引,除了第一维以外的所有维度都需要成为类型的一部分,原因如下:
索引运算符通过首先将索引加到指针上,然后对其进行解引用来操作指针。数组的标识符评估为指向其第一个元素的指针(除非例如与sizeof_Alignof&一起使用),因此在数组上的索引按照您的期望工作。
在单维数组的情况下非常简单。
int a[42];

"a评估为类型为int *的指针,索引的工作方式如下:a [18] => *(a + 18)。"
"现在在一个二维数组中,所有元素都是连续存储的(如果您想将其理解为矩阵,则为“行”之后的“行”),并且使索引“魔法”起作用的是所涉及的类型。例如: "
int a[16][42];

在这里,a元素 类型为 int ()[42](42 个 int 元素的数组)。根据上述规则,在大多数情况下,计算此类型的表达式会再次生成一个 int * 指针。但是对于 a 本身呢?它是一个 int ()[42] 数组,因此 a 将评估为指向 42 个 int 元素的数组的指针:int (*)[42]。然后让我们看一下索引运算符的作用:
a[3][18] => *(*(a + 3) + 18)

假设a是类型为int (*)[42]的地址,这个内部加法可以正确地加上42 * sizeof(int)。如果该类型中的第二个维度未知,则这将是不可能的。

我猜对于n维情况,也很容易推导出类似的例子。


在您的情况下,您有两种可能实现与您想要的类似的东西。
  1. Use a dynamically allocated flat array with size 6*N*N. You can calculate the indices yourself if you save N somewhere.

  2. Somewhat less efficient, but yielding better readable code, you could use an array of pointers to arrays of pointers to int (multiple indirection). You could e.g. do

    int ***a = malloc(6 * sizeof *int);
    for (size_t i = 0; i < 6; ++i)
    {
        a[i] = malloc(N * sizeof *(a[i]));
        for (size_t j = 0; j < N ++j)
        {
            a[i][j] = malloc(N* sizeof *(a[i][j]));
        }
    }
    // add error checking to malloc calls!
    

    Then your accesses will look just like those to a normal 3d array, but it's stored internally as many arrays with pointers to the other arrays instead of in a big contiguous block.

    I don't think it's worth using this many indirections, just to avoid writing e.g. a[2*N*N+5*N+4] to access the element at 2,5,4, so my recommendation would be the first method.


0
在这一行的声明上做一个简单的改变,并保留malloc,可以轻松解决你的问题。
int ***arr = malloc(6 * N * N * sizeof(int));

然而,int *** 是不必要的(也是错误的)。使用一个平坦的数组,这很容易分配:

int *flatarr = malloc(6 * N * N * sizeof(int));

这适用于三维数组,你可以通过访问flatarr[(X*N*N) + (Y*N) + Z]来代替像问题中那样访问arr[X][Y][Z]。事实上,你甚至可以编写一个方便的宏:
#define arr(X,Y,Z) flatarr[((X)*N*N) + ((Y)*N) + (Z)]

这基本上是我在我的语言Cubically中所做的,以允许多种尺寸的立方体。感谢Programming Puzzles & Code Golf用户Dennis给了我这个想法。


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