【问题标题】:What exactly is this method of contiguous C memory allocation doing under the hood?这种连续的 C 内存分配方法到底在做什么?
【发布时间】:2019-12-10 05:22:18
【问题描述】:

我在寻找一种在内存中连续分配大型多维数组的有效方法时遇到了this question。公认的答案表明,对于大小为 sz[0] x sz[1] x sz[2] 的 3D 数组,应该使用这种方法,目前这种方法正在融化我虚弱的大脑:

int (*a)[sz[1]][sz[2]] = calloc(sz[0], sizeof(*a));
...
free(a)

该语句的左侧看起来像分配在堆栈上的int * 的二维数组。右侧是对calloc() 的单个(?!)调用,它在堆上分配int *。由于sizeof(*a)==sizeof(int *)(对吗?)这看起来分配太少而没有任何意义,因为它似乎分配了sz[0]x int * 字节,但它可以索引数组的完整预期大小。

有人可以帮我理解这个定义是如何产生预期结果的吗? C 编译器是否为左侧定义的表中的每个条目重复调用 calloc?如果是这样,如何一次调用free() 就足以摆脱它?结果数组是完全驻留在堆上,还是混合了堆栈上的引用表,指向在堆上分配的内存?

【问题讨论】:

  • sizeof (*a) == sizeof (int [sz1][sz2]) *a 具有“VLA”类型的 sz1 数组 sz2 整数数组
  • 啊!咖啡不够。现在开始变得更有意义了,我将继续考虑它。谢谢
  • 引用的解决方案依赖于可用的 VLA。 VLA 在 C99 之前不存在,在 C11 和之后可能不存在,并且在 C++ 中根本不存在。
  • @alk 很高兴知道,谢谢

标签: c multidimensional-array dynamic-memory-allocation


【解决方案1】:

下面是一些原理类似的代码,一开始可能更容易理解:

typedef int THING[5][6];    // THING means a contiguous array of 5x6 ints

THING arr[4];               // arr is a contiguous array of 4 THINGs
THING *first = &arr[0];     // The expression *first would yield the first thing.

希望您认识到这里的最后两行是对任何数组进行非动态分配的常用语法,并且指的是数组的第一个元素。无论 THING 本身是否是一个数组,这都是一样的。

现在,&arr[0] 指向一个内存位置,它是大小为 4x5x6 的连续整数块的开始。如果您使用动态分配来使该块看起来像:

THING *first = malloc( sizeof(int[4][5][6]) );

如果我们在最后一行展开 typedef,它看起来像:

int (*first)[5][6] = malloc( sizeof(int[4][5][6]) );

您问题中的代码与最后一行相同,除了:

  • 它使用变量而不是硬编码整数(自 C99 起允许使用)。
  • 它使用calloc 而不是malloc
  • 它使用更强大的语法来计算要分配的大小,see here 进行解释。

【讨论】:

  • 这是我正在寻找的解释,谢谢。您能否详细说明您在其他评论中提到的对齐问题?
【解决方案2】:

要不依赖 VLA,但仍使用 一个 连续的内存区域,您可以使用这种方法:

int *** int_array_3d_allocate(size_t x, size_t y, size_t z)
{
  int *** result;

  size_t n = x;
  size_t s = n * sizeof *result; /* x vector of pointer to int** */

  n *= y;
  s += n * sizeof **result; /* x*y vectors of pointer to int* */

  n *= z;
  s += n * sizeof ***result; /* x*y*z int */

  /* allocate it */

  result = malloc(s);
  if (result)
  {
     /* make the int** vector point to the int* vectors: */
     for (size_t i = 0; i < x; ++i)
     {
       result[i] = (int**) ((char*) result) + 
         (x * sizeof *result +
           i * y * sizeof **result);
     }

     /* make the int* vectors point to the int vectors: */
     for (size_t i = 0; i < x*y; ++i)
     {
       ((int**) ((char*) result + x * sizeof *result))[i] = (int*) ((char*) result) +
         (x * sizeof *result + x*y * sizeof **result 
           + i * sizeof ***result);
     }
   }

   return result;
}

上述代码的版本负责正确对齐 int*int** 块:

  #include <stdalign.h>

  int *** int_array_3d_allocate(size_t x, size_t y, size_t z)
  {
    int *** result;

    size_t n = x;
    size_t s = n * sizeof *result; /* x vector of pointer to int** */

    size_t y_off = s % alignof **result 
      ?alignof **result - s % alignof **result :0;

    n *= y;
    s += n * sizeof **result; /* x*y vectors of pointer to int* */

    size_t z_off = s % alignof ***result
      ?alignof ***result - s % alignof ***result :0;

    n *= z;
    s += n * sizeof ***result; /* x*y*z int */

    /* allocate it */

    result = malloc(s);
    if (result)
    {
       /* make the int** vector point to the int* vectors: */
       for (size_t i = 0; i < x; ++i)
       {
         result[i] = (int**) ((char*) result) + y_off +
           (x * sizeof *result +
             i * y * sizeof **result);
       }

       /* make the int* vectors point to the int vectors: */
       for (size_t i = 0; i < x*y; ++i)
       {
         ((int**) ((char*) result + x * sizeof *result + y_off))[i] = (int*) ((char*) result) + y_off +
           (x * sizeof *result + x*y * sizeof **result + z_off +
             + i * sizeof ***result);
       }
     }

     return result;
  }

像这样使用它:

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

int *** int_array_3d_allocate(size_t x, size_t y, size_t z);

int main(void)
{
  const size_t x = 2;
  const size_t y = 3;
  const size_t z = 5;

  int *** int_array_3d = int_array_3d_allocate(x, y, z);
  if (!int_array_3d)
  {
    perror("int_array_3d_allocate() failed");
  }
  else
  {
    for (size_t i = 0; i < x; ++i)
    {
      for (size_t j = 0; j < y; ++j)
      {
        for (size_t k = 0; k < z; ++k)
        {
          int_array_3d[i][j][k] = (int)(i*j*k);
        }
      }
    }

    /* do stuff with the continuous array of ints. 
       Just be aware that the 1st int only is located at address:
       (char* int_array_3d) +
         (x * sizeof *int_array_3d + x*y * sizeof **int_array_3d) 
    */

    free(int_array_3d);
  }
}

【讨论】:

  • 我明白你要做什么,但是发布的代码中有很多编译错误。不过,我明白了它的要点,谢谢。这里做了类似的事情:cboard.cprogramming.com/c-programming/…
  • 我展示的代码和您链接的代码的主要区别在于分配的数量以及 单独 内存区域的数量,这本身就是有争议的,但在链接代码中,所有的都是not。而上面的代码正好使用一个分配,所以描述这个“3d-array”的整个块是一个连续的内存区域,特别是所有 ints 可在一个继续块中访问。
  • 这可能有对齐问题,而且额外的指针也是浪费空间和时间
  • @M.M:需要研究对齐的事情,对。但从概念上讲,如果没有 VLA,我没有其他方法可以做到这一点。
  • 使用单个 [] 运算符和算术来找到正确的索引。如果需要,可以使用辅助宏。你的代码消耗了大量的空间和时间,并且引入了更多可能出错的东西,以获得稍微整洁的语法的“好处”
【解决方案3】:

如果将数组传递给函数,它会衰减为pointer-to-pointer-to-pointer-to-int,使其变得笨拙;还必须传递所有额外的大小信息,或者传递一个指向固定大小的指针;请参阅What is array decaying? 处理具有多个维度的数组的另一种方法是对象,该对象具有在对象中编码的维度。这将在 C90 中编译,

#include <stdlib.h> /* mallc, free, EXIT_ */
#include <errno.h>  /* errno */
#include <stdio.h>  /* perror, printf, fput[c|s] */

struct IntCube { size_t x, y, z; /* C99 supports FAM; would be useful. */ };

/** Returns a `struct IntCube` with `x`, `y`, `z` dimensions or null and
 `errno` may be set. The caller is responsible for calling `free`. */
static struct IntCube *IntCube(const size_t x, const size_t y, const size_t z) {
    struct IntCube *cube;
    size_t xy_size, xyz_size, data_size, cube_size;

    if(!x || !y || !z) return 0;

    /* Check for overflow; <https://stackoverflow.com/q/1815367/2472827>. */
    xy_size = x * y;
    xyz_size = xy_size * z;
    data_size = xyz_size * sizeof(int);
    cube_size = sizeof cube + data_size;
    if(xy_size / x != y
        || xyz_size / xy_size != z
        || data_size / xyz_size != sizeof(int)
        || cube_size < data_size) { errno = ERANGE; return 0; }

    /* Allocate memory. */
    if(!(cube = malloc(cube_size))) return 0; /* POSIX has defined errors. */
    cube->x = x;
    cube->y = y;
    cube->z = z;
    return cube;
}

static int *int_cube_get(const struct IntCube *cube,
    const size_t x, const size_t y, const size_t z) {
    return (int *)(cube + 1) + z * cube->y * cube->x + y * cube->x + x;
}

typedef void (*IntCubeAction)(const size_t x, const size_t y, const size_t z,
    int *pnumber);

typedef void (*BinaryAction)(int bin);

/** Goes through `cube` and performs `action` on each number. It will call
 optional binary action `bin` each time there is an
 start(false)/end(true)-of-x/y. */
static void IntCubeForEach(struct IntCube *const cube,
    const IntCubeAction action, const BinaryAction bin) {
    size_t x, y, z;
    if(!cube || !action) return;
    for(z = 0; z < cube->z; z++) {
        if(bin) bin(0);
        for(y = 0; y < cube->y; y++) {
            if(bin) bin(0);
            for(x = 0; x < cube->x; x++) {
                action(x, y, z, int_cube_get(cube, x, y, z));
            }
            if(bin) bin(1);
        }
        if(bin) bin(1);
    }
}

/** @implements IntCubeAction */
static void fill_with_xyz(const size_t x, const size_t y, const size_t z,
    int *pnumber) {
    *pnumber = (x + 1) * (y + 1) * (z + 1);
}

/** @implements IntCubeAction */
static void print_cube(const size_t x, const size_t y, const size_t z,
    int *pnumber) {
    (void)y, (void)z;
    printf("%s%d", x ? ", " : "", *pnumber);
}

/** @implements BinaryAction */
static void print_cube_corners(int bin) {
    printf("%s", bin ? " }" : "{ ");
}

int main(void) {
    struct IntCube *cube = 0;
    int status = EXIT_FAILURE;

    if(!(cube = IntCube(4, 3, 3))) goto catch;
    IntCubeForEach(cube, &fill_with_xyz, 0);
    IntCubeForEach(cube, &print_cube, &print_cube_corners);
    fputc('\n', stdout);
    status = EXIT_SUCCESS;
    goto finally;

catch:
    perror("Cube");

finally:
    free(cube);
    return status;
}

{ { 1, 2, 3, 4 }{ 2, 4, 6, 8 }{ 3, 6, 9, 12 } }{ { 2, 4, 6, 8 }{ 4, 8, 12, 16 }{ 6, 12, 18, 24 } }{ { 3, 6, 9, 12 }{ 6, 12, 18, 24 }{ 9, 18, 27, 36 } }

这会创建对struct IntCube 的依赖,但有了依赖,就可以在运行时计算大小。

【讨论】:

  • 这里没有pointer-to-pointer-to-pointer-to-int或类似的东西
  • 这不是 int *** 吗?
  • 问题中没有int***(也没有衰减到int***
  • 我的印象是int (*a)[sz[1]][sz[2]]衰减为int ***;这不是真的吗?此外,在原始问题的链接中,int ***calloc_3d_arr(int sizes[3])
猜你喜欢
  • 1970-01-01
  • 2014-10-29
  • 1970-01-01
  • 2012-07-03
  • 2018-11-16
  • 2015-01-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多