编译器可以将多个 malloc 调用合并为一个吗?

Can compiler combine multiple malloc calls into one?

假设我们有以下两段代码:

    int *a = (int *)malloc(sizeof(*a));
    int *b = (int *)malloc(sizeof(*b));

    int *a = (int *)malloc(2 * sizeof(*a));
    int *b = a + 1;

它们都在堆上分配了两个整数并且(假设正常使用)它们应该是等价的。第一个似乎更慢,因为它调用 malloc 两次并且可以产生对缓存更友好的代码。然而,第二个可能是不安全的,因为我们可能会意外地通过递增 a 并写入结果指针来覆盖 b 指向的值(或者恶意的人可以通过知道 a 的位置立即更改 b 的值)。

上面的说法可能不正确(例如速度在这里被质疑:Minimizing the amount of malloc() calls improves performance?)但我的问题只是:编译器可以做这种类型的转换还是两者之间有根本不同两者按标准?如果可能的话,哪些编译器标志(比如说 gcc)可以允许它?

不,它不能,因为编译器(通常)不知道 ab 什么时候可能会得到 free(),如果它分配它们两者都作为单个分配的一部分,那么它也需要同时 free() 它们。

这可能永远不会发生的原因有很多,但最重要的是 lifetimes,其中这些分配如果独立进行,则可以独立释放。如果一起制作,它们将被锁定为相同的生命周期。

这种细微差别最好由开发人员表达,而不是由编译器决定。

第二个“不安全”是因为您可以覆盖值吗?在 C 中,并扩展为 C++,该语言并不能保护您免受不良编程的侵害。您可以随时使用任何必要的方式搬起石头砸自己的脚:

int a;
int b;

int* p = &a;
p[1] = 9; // Bullet, meet foot

(&b)[-1] = 9; // Why not?

如果你想分配 N 的东西一定要使用 calloc() 来表达它,或者适当大小的 malloc()。除非有充分的理由,否则单独分配是没有意义的。

通常您不会分配一个 int,这有点没用,但在某些情况下,这可能是唯一合理的选择。通常它是较大的块,例如完整的 struct 或字符缓冲区。

首先:

int *a = (int *)malloc(8);
int *b = a + 4;

不是你想的那样。你想要:

int *a = malloc(sizeof(*a) * 2);
int *b = a + 1;

说明指针运算是你需要学习的东西。

其次:编译器​​不会更改您的代码中的任何内容,也不会将任何函数调用合并为一个。您尝试实现的是微优化。如果您想使用更大的内存块,只需使用数组即可。

int *a = malloc(sizeof(*a) * 2);

a[0] = 5;
a[1] = 6;
/* some other code */

free(a);

不要使用“magic”数字是对象的唯一sizeof malloc。不要转换 malloc

的结果

我已经用 bignum 库做到了这一点,但你只释放了一个指针。

//initialization every time program runs
extern bignum_t *scratch00;  //these are useful for taylor series, etc.
extern bignum_t *scratch01;
extern bignum_t *scratch02;

.
.
.

bignum_t *bn_malloc(int bignums)
{
    return(malloc(bignums * bn_numbytes));
}

.
.
.

//bignums specific to the program being written at the moment
bignum_t *numerator;
bignum_t *denom;
bignum_t *denom_add;
bignum_t *accum;
bignum_t *term;

.
.
.

numerator = bn_malloc(1);
denom = bn_malloc(1);
denom_add = bn_malloc(1);
accum = bn_malloc(1);
term = bn_malloc(1);

实际上,不会,编译器永远不会自动将 2 个 malloc() 调用合并为一个 malloc() 调用。每次调用 malloc() returns 新内存块的地址,不能保证分配的块位于彼此靠近的任何位置,每个分配的块必须 free()'d 单独.因此,没有编译器会假设多个分配块之间的关系并尝试为您优化它们的分配。

现在,可能非常简化的用例中,分配和解除分配在同一范围内,如果可以证明这样做是安全的,那么编译器供应商可能决定尝试优化,即:

void doIt()
{
    int *a = (int *)malloc(sizeof(*a));
    int *b = (int *)malloc(sizeof(*b));
    ...
    free(a);
    free(b);
}

可以变成:

void doIt()
{
    void *ptr = malloc(sizeof(int) * 2);
    int *a = (int *)ptr;
    int *b = a + 1;
    ...
    free(ptr);
}

但实际上,没有编译器供应商会真正尝试这样做。为了这么小的收获,不值得付出努力或冒险。而且它无论如何也不会在更复杂的场景中工作,例如:

void doIt()
{
    int *a = (int *)malloc(sizeof(*a));
    int *b = (int *)malloc(sizeof(*b));
    ...
    UseAndFree(a, b);
}

void UseAndFree(int *a, int *b)
{
    ...
    free(a);
    free(b);
}