我有一个关于 C 语言动态内存分配的问题。 我目前正在研究 C 语言中的人工神经网络嵌入。 我找到了一个名为genann的现有项目,在那里我注意到了一种我不熟悉的内存分配方法。
考虑一个结构:
typedef struct
{
int an, bn;
double *a, *b
} foo;
还有一个初始化函数:
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 缩小 1 并重新分配整个内存块,则最后两个 b 值将丢失(而不是一个 a,一个 b(。
我的结论是,最好以第一种方式分配内存,因为第二种方式几乎只有缺点。初始化函数之一是不良做法吗?我是否错过了什么,这将使第二个功能更好?也许他们在项目中使用第二个有一个特殊的原因?
提前谢谢。
如果您分配并释放大量这些结构,节省的成本可能会增加一些可观的东西。
它使用的空间少一点,因为每个分配的块都有一些记录其大小的簿记。它还可以减少内存碎片。
此外,这样做可确保a
和b
数组在内存中靠得很近。如果它们经常一起使用,这可以提高缓存命中率。
库实现者经常进行这些微优化,因为他们无法预测库的使用方式,并且他们希望在所有情况下都尽可能好地工作。在编写自己的应用程序时,可以更好地了解哪些代码将位于对性能优化很重要的内部循环中。
第二种方法问题。 以下内容可能会由于对齐而失败。
f->a = (double*)((char*)f + sizeof(*f));
malloc()
返回的指针对所有对象指针都有效。 计算((char*)f + sizeof(*f))
可能不符合对齐要求。在这种情况下,事情很可能会奏效,但对于其他typedef
则不然。
从 C99 开始,代码可以使用灵活的数组成员来获得单个分配的优势,而不会冒 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;
}