在一个块中为不同类型分配内存
Allocate memory for different types in one block
我对 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;
}
这是一个与上述项目不同的初始化函数:
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)。
我的结论是,以第一种方式分配内存更好,因为第二种方式几乎只有缺点。初始化函数之一是不好的做法吗?我是否遗漏了什么,这会使第二个功能更好?也许他们在项目中使用第二个是有特殊原因的?
提前致谢。
如果您分配并释放大量这些结构,节省下来的钱可能会非常可观。
它用得少一点 space,因为每个分配的块都有一些记录其大小的簿记。它还可以减少内存碎片。
此外,这样做可确保 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;
}
我对 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;
}
这是一个与上述项目不同的初始化函数:
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)。
我的结论是,以第一种方式分配内存更好,因为第二种方式几乎只有缺点。初始化函数之一是不好的做法吗?我是否遗漏了什么,这会使第二个功能更好?也许他们在项目中使用第二个是有特殊原因的?
提前致谢。
如果您分配并释放大量这些结构,节省下来的钱可能会非常可观。
它用得少一点 space,因为每个分配的块都有一些记录其大小的簿记。它还可以减少内存碎片。
此外,这样做可确保 a
和 b
数组在内存中靠得很近。如果它们经常一起使用,这可以提高缓存命中率。
库实施者经常进行这些微优化,因为他们无法预测库将如何使用,并且他们希望在所有情况下都尽可能地工作。当您编写自己的应用程序时,您可以更好地了解哪些代码将处于对性能调整很重要的内部循环中。
第二个方法问题。由于对齐,以下可能会失败。
f->a = (double*)((char*)f + sizeof(*f));
malloc()
返回的指针对所有对象指针都有效。计算出的 ((char*)f + sizeof(*f))
可能不符合对齐要求。在这种情况下,很可能事情会奏效,但对于其他typedef
情况则不然。
从 C99 开始,代码可以使用 灵活的数组成员 来获得单个分配的
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;
}