【问题标题】:Allocate space for a 3 dimensional array in C在 C 中为 3 维数组分配空间
【发布时间】:2017-06-03 01:14:23
【问题描述】:

这个问题已经回答了here,但对我来说,有些事情仍然悬而未决,讨论太老了,任何人都无法回复 cmets,此外我还想问一下我的实施出了什么问题。

无论如何,问题设置。我想为尺寸为3, h, w 的3D 数组分配空间。我在做什么:

int ***a = (int***)malloc(3 * sizeof(int*));
for (int i = 0; i < 3; i++)
{
   a[i] = (int **) malloc(height * sizeof(int*)); 
}

我理解这是为三个 3 int** 创建第一个空间,然后分配高度 int**; 然后:

for (int i = 0; i < 3; i++)
{
  a[i][0] = (int *) malloc(height * width * sizeof(int *));
  for (int j = 1; j < height; j++)
   {
     a[i][j] = a[i][j-1] + width;
   }
}

所以现在我认为每个a[i][0] 基本上是一个用于高度和宽度大小的二维数组的空间;此外,a[i][j] 的其余部分 j != 0 被初始化为 a[i][0]j th 行;

然后我通过以下方式初始化值:

for (int c = 0; c < 3; c++){
  for (int i = 0; i < height; i++){
    for (int j = 0; j < width; j++){
      a[c][i][j] = i+j+c;

然而这是不正确的。我想将此作为扩展练习,以通过以下方式分配 2D 数组:

int *a[height]; 
a[0] = (int*)malloc(height * width * sizeof(int));
for (int i = 1; i < height; i++){
  a[i] = a[i-1] + widht;
}

这样 a 是一个长度为高度的指针数组,a[0] 分配给整个二维数组,随后的指针指向它们各自的行,我可以通过调用 a[i][j] = some number; 简单地写入它们

我想做的是将这个 2D 想法扩展到 3D 案例,但遗憾的是我失败了。

【问题讨论】:

  • int (*a)[height][width] = malloc(3 * sizeof *a); 应该给你一个 3d 数组。
  • 这不会创建一个指向高度、宽度大小的二维数组的指针吗?
  • 是的,a 是一个指向二维数组的指针,可以与a[i][j][k] 一起使用。
  • 你为什么不能只做一维分配,然后自己做索引?更紧凑,我不相信这两个乘法比所有内存遍历更昂贵。更不用说更有效的堆使用了。
  • @unwind 那 would 是处理这个问题的更好方法,但是,我想要的只是一些内存管理的棘手部分的练习,以及数组到指针衰减和诸如此类。

标签: c arrays pointers dynamic-memory-allocation


【解决方案1】:
int ***a = (int***)malloc(3 * sizeof(int*));

这不会分配任何有意义的东西。特别是,它不分配 3D 数组,甚至不分配 3D 数组的一部分。它为 3 个整数指针分配空间,这没有任何意义。 int*** 也没有任何意义。

改为分配一个 3D 数组:

int (*a)[y][z] = malloc( sizeof(int[x][y][z]) );

for(int i=0; i<x; i++)
  for(int j=0; j<y; j++)
    for(int k=0; k<z; k++)
      a[i][j][k] = something;

free(a);

编辑(以下文字中的任何讽刺语气都是故意的)

为了三星级程序员拒绝接受任何少于三个间接级别的解决方案的利益,这里有一个如何正确进行三星级编程的示例。

不幸的是,它还不包含无条件向上分支、内存泄漏或复杂的指针运算。但是对于一个三星级的程序员来说,这样开心的事情以后再补充当然是微不足道的。然而,它确实保证数据缓存未命中,因此程序将以与没有任何数据缓存的系统相同的性能运行。

也可以考虑将分配的指针对指针作为参数传递,从而达到4星!!!

[如中,指针指向的方法是非常糟糕的做法,但如果你出于未知的原因坚持使用它,你还不如正确地使用它。]

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


void free_int_3d (int*** ppp, size_t X, size_t Y, size_t Z)
{
  (void)Z;

  if(ppp == NULL)
  {
    return ;
  }

  for(size_t x=0; x < X; x++)
  {
    if(ppp[x] != NULL)
    {
      for(size_t y=0; y < Y; y++)
      {
        if(ppp[x][y] != NULL)
        {
          free(ppp[x][y]);
        }
      }
      free(ppp[x]);
    }
  }
  free(ppp);
}


#define malloc_with_cleanup(ptr, size)     \
ptr = malloc(size);                        \
if(ptr == NULL)                            \
{                                          \
  free_int_3d(result, X, Y, Z);            \
  return NULL;                             \
}

int*** int_alloc_3d (size_t X, size_t Y, size_t Z)
{
  int*** result = NULL;

  malloc_with_cleanup(result, sizeof(int**[X]));
  for(size_t x=0; x<X; x++)
  {
    malloc_with_cleanup(result[x], sizeof(int*[Y]));
    for(size_t y=0; y<Y; y++)
    {
      malloc_with_cleanup(result[x][y], sizeof(int[Z]));
    }
  }
  return result;
}


int main (void)
{
  const size_t X = 5;
  const size_t Y = 3;
  const size_t Z = 4;

  int*** ppp = int_alloc_3d(X, Y, Z);

  for(size_t i=0; i<X; i++)
  {
    for(size_t j=0; j<Y; j++)
    {
      for(size_t k=0; k<Z; k++)
      {
        ppp[i][j][k] = (int)k;
        printf("%d ", ppp[i][j][k]);
      }
      printf("\n");
    }
    printf("\n");
  }

  free_int_3d(ppp, X, Y, Z);

  return 0;
}

输出:

0 1 2 3
0 1 2 3
0 1 2 3

0 1 2 3
0 1 2 3
0 1 2 3

0 1 2 3
0 1 2 3
0 1 2 3

0 1 2 3
0 1 2 3
0 1 2 3

0 1 2 3
0 1 2 3
0 1 2 3

【讨论】:

  • 我知道它不会分配整个数组,我想做的是分配 3 个 int** 类型的指针
  • @VahagnTumanyan 为什么?你说你想要一个 3D 数组。在这种情况下,指针对指针没有意义。
  • 简单地作为处理任意维度指针中的几个指针的练习。此外,对malloc 使用更正确的语法(语义?),例如将返回的void * 转换为正确的指针类型并使用(number_of_elems * sizeof(type)),而不是简单地分配一个数组。
  • @VahagnTumanyan 确保你 1) 实际上没有使用 C++ 编译器,在这种情况下你会得到奇怪的错误,并且 2) 你不是不小心使用了 27 岁的版本gcc,通过添加编译器选项-std=c99-std=c11
  • @GregSchmit 同样,这些不是 3D 数组,因为内存不是相邻分配的。它是指向数据的查找表的查找表。就个人而言,我不太明白为什么慢速程序比执行相同任务的快速程序更有趣。我的乐趣是对上面的两个 sn-ps 进行基准测试,然后在我测量第二个的执行时间时开怀大笑。
【解决方案2】:

让我们先想象一下你想要做什么,然后我会展示代码来实现:

  int ***         int **                int *                  int 

  +---+           +---+                 +---+                  +---+
a |   | ---> a[0] |   | ------> a[0][0] |   | ----> a[0][0][0] |   |
  +---+           +---+                 +---+                  +---+
             a[1] |   | ----+   a[0][1] |   | -+    a[0][0][1] |   |
                  +---+     |           +---+  |               +---+
             a[2] |   |     |            ...   |                ...
                  +---+     |           +---+  |               +---+
                            | a[0][h-1] |   |  |  a[0][0][w-1] |   | 
                            |           +---+  |               +---+
                            |                  |
                            |           +---+  |               +---+
                            +-> a[1][0] |   |  +--> a[0][1][0] |   |
                                        +---+                  +---+
                                a[1][1] |   |       a[0][1][1] |   |
                                        +---+                  +---+
                                         ...                    ...
                                        +---+                  +---+
                              a[1][h-1] |   |     a[0][1][w-1] |   |
                                        +---+                  +---+

类型:

  • 每个a[i][j][k] 都有int 类型;
  • 每个a[i][j]指向int对象序列的第一个元素,所以它必须有int *类型;
  • 每个a[i]指向int *对象序列的第一个元素,所以它必须有int **类型;
  • a 指向int ** 对象序列的第一个元素,因此它的类型必须为int ***

由于您正在进行零碎的嵌套分配,因此您需要检查每个malloc 调用的结果,如果出现错误,您需要在退出之前清理任何先前分配的内存,否则您会冒内存风险泄漏。不幸的是,没有真正干净或优雅的方法可以做到这一点 - 你要么随身携带一面旗帜并进行大量额外测试,要么投入几个gotos。我将展示两种不同的方法,它们都不是那么漂亮。

第一种方法 - 当我们分配每个 a[i] 时,我们也分配每个 a[i][j](“深度优先”方法)并初始化每个 a[i][j][k]。如果a[i][j] 上的分配失败,我们必须释放所有a[i][0]a[i][j-1],然后释放a[i],然后对a[0]a[i-1] 中的每一个重复该过程:

/**
 * Depth-first approach: allocate each a[i][j] with each a[i]
 */
int ***alloc1( size_t pages, size_t height, size_t width )
{
  size_t i, j, k;

  int ***a = malloc( sizeof *a * pages ); // allocate space for N int **
                                          // objects, where N == pages

  if ( !a )
    return NULL;

  for ( i = 0; i < pages; i++ )
  {
    a[i] = malloc( sizeof *a[i] * height );  // for each a[i], allocate space for
    if ( !a[i] )                             // N int * objects, where N == height
      goto cleanup_1;

    for ( j = 0; j < height; j++ )
    {
      a[i][j] = malloc( sizeof *a[i][j] * width ); // for each a[i][j], allocate      
      if ( !a[i][j] )                              // space for N int objects,
        goto cleanup_2;                            // where N == w

      for ( k = 0; k < width; k++ )
        a[i][j][k] = initial_value( i, j, k );
    }
  }
  goto done;

/**
 * Free all of a[i][0] through a[i][j-1], then free a[i]
 */
cleanup_2:
  while ( j-- )
    free( a[i][j] );
  free( a[i] );

/**
 * Free all of a[0] through a[i-1], then free a
 */
cleanup_1:
  while ( i-- )
  {
    j = height;
    goto cleanup_2;
  }
  free( a );
  a = NULL;

done:
  return a;  // at this point, a is either a valid pointer or NULL.
}

是的,这段代码包含gotos,它违反了我的一条宠物规则(永远不要向后分支)。但是,分配代码和清理代码之间有一个相当清晰的分离,我们不会在清理部分重复我们自己。 cleanup_2 释放页面内的所有行以及页面本身; cleanup_1 释放所有页面。 cleanup_2“落入”cleanup_1

这是第二种方法——首先我们在分配任何a[i][j]之前分配所有a[i],然后我们确保在初始化数组内容之前成功分配所有a[i][j]

/**
 * Breadth-first approach; allocate all a[i], then all a[i][j], then initialize
 */
int ***alloc2( size_t pages, size_t height, size_t width )
{
  size_t i, j, k;

  /**
   * Allocate space for N int ** objects, where N == pages
   */
  int ***a = malloc( sizeof *a * pages );

  if ( !a )
    return NULL;                        // allocation failed for initial sequence, return NULL

  for ( i = 0; i < pages; i++ )  // For each a[i], allocate N objects of type
  {                              // int *, where N == height
    a[i] = malloc( sizeof *a[i] * height );
    if ( !a[i] )
      break;
  }

  if ( i < pages )
  {
    while ( i-- )                       // allocation of a[i] failed, free up a[0] through a[i-1]
      free( a[i] );
    free( a );                          // free a
    return NULL;
  }

  for ( i = 0; i < pages; i++ )    
  {
    for ( j = 0; j < height; j++ )
    {
      a[i][j] = malloc( sizeof *a[i][j] * width ); // for each a[i][j], allocate    
      if ( !a[i][j] )                             // space for N int objects, 
        break;                                    // where N == width
    }
  }

  if ( j < h )
  {
    do
    {
      while ( j-- )                     // allocation of a[i][j] failed, free up a[i][0] through a[i][j-1]
        free( a[i][j] );                // repeat for all of a[0] through a[i-1].
      free( a[i] );
      j = h;
    } while ( i-- );
    free( a );                          // free a
    return NULL;
  }

  /**
   * All allocations were successful, initialize array contents
   */    
  for ( i = 0; i < pages; i++ )
    for ( j = 0; j < height; j++ )
      for ( k = 0; k < width; k++ )
        a[i][j][k] = initial_value( i, j, k );

  return a;
}

没有gotos!但是代码并没有像 IMO 一样“流动”。分配代码和清理代码之间没有那么清晰的分离,清理部分之间有一些重复。

请注意,对于这两种方法,内存不是连续的 - 紧跟在 a[0][0][h-1] 之后的元素不会是 a[0][1][0]。如果您需要所有数组元素在内存中相邻,则需要使用其他显示的方法:

int (*a)[h][w] = malloc( sizeof *a * 3 );

需要注意的是,如果 hw 很大,您可能没有足够的连续分配内存来满足请求。

【讨论】:

  • 请注意,图片显示的不是3D数组,而是查找表指向的一些分散的段。所以如果你想要一个 3D 数组,这不是正确的解决方案。此外,将三星级节目与意大利面条节目相结合可能不是最好的主意。您可以在调用 malloc 之前简单地将所有指针初始化为 NULL,而不是意大利面条 goto。这样,无论它们是否指向分配的内存,您都可以将它们传递给free()。那么只需要一个清理程序。
  • @Lundin:OP 想要一个将 非连续 分配扩展到 3 维的示例,这就是我提供的。
  • 我怀疑这个问题是关于 XY 问题的。 OP 询问如何使用指针对指针查找表进行动态内存分配。虽然他们试图解决的实际问题可能只是“如何动态分配 3D 数组”。
【解决方案3】:

为了解决 OP 的更高目标,我怀疑指向 2D 数组的指针就足够了 @mch

int (*a)[height][width] = malloc(sizeof *a * 3);

 a[c][i][j] = i + j + c;

但根据 OP 的要求....

...为 3D 数组分配空间...

代码通常如何创建 3D 数组?
下面的代码使用 C99 中提供的 可变长度数组 来实现这一点。
(C11 可选支持 VLA)

int a1[3][height][width];
// and assign it accordingly
for (int c = 0; c < 3; c++) {
  for (int i = 0; i < height; i++) {
    for (int j = 0; j < width; j++) {
      a1[c][i][j] = i + j + c;

然而 OP 想要为这样的数组分配 空间。

malloc() 返回一个指针。指向已分配内存的指针。所以我们需要创建一个指向这样一个数组的指针。一旦我们这样做了,分配和元素分配就很容易了。

int (*a2)[3][height][width] = malloc(sizeof *a2);

// 3 nested for loop as above
    (*a2)[c][i][j] = i + j + c;

示例代码

void VT_3_dimensional_array(int height, int width) {
  assert(height > 0 && width > 0);

  int (*a2)[3][height][width] = malloc(sizeof *a2);
  printf("%p %zu\n", (void*) a2, sizeof *a2);
  if (a2 == NULL) {
    perror("Out of memory");
    exit(EXIT_FAILURE);
  }
  for (int c = 0; c < 3; c++) {
    for (int i = 0; i < height; i++) {
      for (int j = 0; j < width; j++) {
        (*a2)[c][i][j] = i + j + c;
      }
    }
  }

  // use a;
  for (int c = 0; c < 3; c++) {
    for (int i = 0; i < height; i++) {
      putchar('(');
      for (int j = 0; j < width; j++) {
        printf(" %X", (*a2)[c][i][j]);
      }
      putchar(')');
    }
    puts("");
  }

  free(a2);
}

int main() {
  VT_3_dimensional_array(5, 7);
  return 0;
}

输出

0x80071980 420
( 0 1 2 3 4 5 6)( 1 2 3 4 5 6 7)( 2 3 4 5 6 7 8)( 3 4 5 6 7 8 9)( 4 5 6 7 8 9 A)
( 1 2 3 4 5 6 7)( 2 3 4 5 6 7 8)( 3 4 5 6 7 8 9)( 4 5 6 7 8 9 A)( 5 6 7 8 9 A B)
( 2 3 4 5 6 7 8)( 3 4 5 6 7 8 9)( 4 5 6 7 8 9 A)( 5 6 7 8 9 A B)( 6 7 8 9 A B C)

注意,从技术上讲,OP 的代码使用了错误的大小。通常sizeof(int*) 会与sizeof(int**) 相同,所以没有太大的危害。然而,这确实表明对分配的混淆。

// wrong type  -----------------------v  should be int**
int ***a = (int***)malloc(3 * sizeof(int*));

【讨论】:

  • 我想知道是谁投了反对票。对我来说似乎完全合法。但我想,您可能已经通过将最后一段移到顶部并稍后继续解释来阻止该投票者。
  • @cmaster OK 答案已修改。
  • 使用指向 3d VLA 的指针和 mallocing 空间而不是首先声明 3d VLA 可以获得什么?我想malloced 数组可能会更大。
  • @DavidBowling 无法检测到 VLA 故障,malloc() 故障是。我认为上述任何一种方法都没有太大的价值,我更喜欢简单的int *a = malloc(sizeof *a * 3 * height * width);。然而 OP 询问如何为 3D 数组分配空间,所以这个答案集中在这个。
猜你喜欢
  • 2017-03-15
  • 1970-01-01
  • 2016-04-19
  • 2018-04-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多