在一个块中为不同类型分配内存

时间:2018-05-24 16:40:35

标签: c dynamic-memory-allocation

我对C中的动态内存分配有疑问。 我目前正在研究C中的人工神经网络实现。 我找到了现有的项目genann,我注意到了一种我不熟悉的memery分配方法。

考虑一个结构:

typedef struct
{
    int an, bn;
    double *a, *b
} foo;

和init函数:

foo *foo_init(int an, int bn)
{
    foo *f;

    f = malloc(sizeof(*f));
    f->an = an;
    f->bn = bn;

    f->a = malloc(an*sizeof(*f->a));
    f->b = malloc(bn*sizeof(*f->b));

    return f;
}

这是一个不同的init函数,就像上面提到的项目一样:

foo *foo_init(int an, int bn)
{
    foo *f;

    f = malloc(sizeof(*f) + an*sizeof(*f->a) + bn*sizeof(*f->b));
    f->an = an;
    f->bn = bn;

    f->a = (double*)((char*)f + sizeof(*f)); // Could be f->a = (double*)(f + 1); ?
    f->b = f->a + an;

    return f;
}

所以在想这两种方法之间有什么区别。我能想到的第二种方法的唯一优点是我只需要分配和释放一个内存块,但由于它只是一个可能只被调用一次的init函数,因此性能差异应该是微不足道的。另一方面,不同类型的指针指向相同的记忆块,但我认为这并不违反严格的别名规则,因为它们指向块内的不同内存(?)。调整大小可能很困难,因为它不能像第一个init函数那样使用简单的realloc。例如,如果我想将a和b缩小一个并重新分配整个内存块,则最后两个b值将丢失(而不是一个a,一个b)。

我的结论是,以第一种方式分配内存更好,因为第二种方式几乎只有缺点。 init函数之一是不好的做法吗?我错过了什么,这将使第二个功能更好?也许有一个特殊原因导致他们在项目中使用第二个?

提前致谢。

2 个答案:

答案 0 :(得分:4)

如果您分配并释放大量这些结构,那么节省的费用可能会增加。

它占用的空间少一些,因为每个已分配的块都有一些记录其大小的簿记。它还可以减少内存碎片。

此外,这样做可确保ab数组在内存中紧密相连。如果它们经常一起使用,则可以提高缓存命中率。

图书馆实施者经常进行这些微观优化,因为他们无法预测图书馆的使用方式,他们希望在所有情况下尽可能地工作。当您编写自己的应用程序时,您可以更好地了解内部循环中哪些代码对性能调优很重要。

答案 1 :(得分:1)

第二个问题。由于对齐,以下内容可能会失败。

f->a = (double*)((char*)f + sizeof(*f));

malloc()返回的指针对所有对象指针都有效。计算的((char*)f + sizeof(*f))可能不符合对齐要求。在 this 的情况下,高度可能事情会起作用,但对于其他typedef则不然。

从C99开始,代码可以使用灵活的数组成员来获取单个分配的advantages,而不会冒UB的风险。语法也更简单。

typedef struct {
  int an, bn;
  double *a, *b;
  // Padding will be added here as needed to meet `data[]` alignment needs
  double data[];
} foo;

foo *foo_init(int an, int bn) {
  // `sizeof *f` includes space for `an,bn,a,b` 
  //  and optional alignment padding up to `data`, but not `data`.
  foo *f = malloc(sizeof *f + (an + bn) * sizeof *(f->data));
  if (f) {
    f->an = an;
    f->bn = bn;
    f->a = (double*) (f + 1);  // Guaranteed to align to f->data
    f->b = f->a + an;
  }
  return f;
}

考虑size_t而不是int an, bn

在C99之前使用union进行此操作的方法有很多种。像

这样的东西
typedef union {
  foo f;
  double data;
} both;

foo *foo_init(int an, int bn) {
  both *p = malloc(sizeof(both) + (an + bn) * sizeof *(f->data));
  foo *f = (foo *) p;
  if (f) {
    f->an = an;
    f->bn = bn;
    //                v--- Note p
    f->a = (double*) (p + 1);  // Guaranteed to align to p->data type
    f->b = f->a + an;
  }
  return f;
}