10

即使在阅读了很多关于严格别名规则的内容之后,我仍然感到困惑。据我了解,不可能实现遵循这些规则的合理内存分配器,因为 malloc 永远不能重用释放的内存,因为内存可用于在每次分配时存储不同的类型。

显然这是不对的。我错过了什么?您如何实现遵循严格别名的分配器(或内存池)?

谢谢。

编辑:让我用一个愚蠢的简单例子来澄清我的问题:

// s == 0 frees the pool
void *my_custom_allocator(size_t s) {
    static void *pool = malloc(1000);
    static int in_use = FALSE;
    if( in_use || s > 1000 ) return NULL;
    if( s == 0 ) {
        in_use = FALSE;
        return NULL;
    }
    in_use = TRUE;
    return pool;
}

main() {
    int *i = my_custom_allocator(sizeof(int));
    //use int
    my_custom_allocator(0);
    float *f = my_custom_allocator(sizeof(float)); //not allowed...
}
4

4 回答 4

11

我不认为你是对的。即使是最严格的严格别名规则也只会在实际分配内存用于某个目的时才算在内。一旦分配的块被释放回堆free,就不应该有对它的引用,它可以被 再次发出malloc

并且void*返回的 bymalloc不受严格的别名规则的约束,因为标准明确指出 void 指针可以转换为任何其他类型的指针(然后再返回)。C99 第 7.20.3 节规定:

如果分配成功,则返回的指针经过适当对齐,以便可以将其分配给指向任何类型对象的指针,然后用于访问已分配空间中的此类对象或此类对象的数组(直到空间被显式释放) .


就您实际上没有将内存返回到堆的更新(示例)而言,我认为您会感到困惑,因为分配的对象被特殊处理。如果您参考6.5/6C99,您会看到:

访问其存储值的对象的有效类型是对象的声明类型,如果有的话(脚注 75:分配的对象没有声明的类型)。

重读那个脚注,这很重要。

如果通过具有非字符类型类型的左值将值存储到没有声明类型的对象中,则左值的类型将成为该访问的对象的有效类型以及不修改该类型的后续访问储值。

如果使用 memcpy 或 memmove 将值复制到没有声明类型的对象中,或者复制为字符类型的数组,则对于该访问和不修改该值的后续访问,修改对象的有效类型是从中复制值的对象的有效类型(如果有的话)。

对于没有声明类型的对象的所有其他访问,对象的有效类型只是用于访问的左值的类型。

换句话说,分配的块内容将成为您放入其中的数据项的类型。

如果你把 afloat放在那里,你应该只将它作为一个float(或兼容的类型)来访问。如果你输入一个int,你应该只将它作为一个int(或兼容的类型)来处理。

您不应该做的一件事是将特定类型的变量放入该内存中,然后尝试将其视为不同的类型 - 原因之一是允许对象具有陷阱表示(这会导致未定义的行为)和由于将相同的对象视为不同的类型,可能会出现这些表示。

因此,如果您要int在代码中的释放之前将其存储在其中,然后将其重新分配为float指针,则您不应尝试使用浮点数,直到您实际将一个浮点数放在那里。直到那时,分配的类型还没有float

于 2011-10-07T12:19:33.533 回答
3

标准 C 没有定义任何有效的方法,用户编写的内存分配器可以安全地获取已用作一种类型的内存区域并使其安全地用作另一种类型。保证 C 中的结构不会捕获表示 - 如果不能安全地复制包含不确定值的字段的结构,这种保证就没有什么意义。

困难在于给定这样的结构和功能:

struct someStruct {unsigned char count; unsigned char dat[7]; }
void useStruct(struct someStruct s); // Pass by value

应该可以像这样调用它:

someStruct *p = malloc(sizeof *p);
p->count = 1;
p->dat[0] = 42;
useStruct(*p);

without having to write all of the fields of the allocated structure first. Although malloc will guarantee that the allocation block it returns may be used by any type, there is no way for user-written memory-management functions to enable such reuse of storage without either clearing it in bytewise fashion (using a loop or memset) or else using free() and malloc() to recycle the storage.

于 2017-01-08T22:59:09.770 回答
2

我发布这个答案是为了测试我对严格别名的理解:

严格的别名仅在发生实际读取和写入时才重要。正如同时使用多个不同类型的联合成员是未定义的行为一样,指针也是如此:您不能使用不同类型的指针访问相同的内存,原因与您不能使用联合的原因相同。

如果您只认为其中一个指针是活动的,那么这不是问题。

  • 因此,如果您通过 a 写入int*并通过 a 读取int*,就可以了。
  • 如果您使用 an 编写int*并通读 an float*,那就不好了。
  • 如果您使用 an编写,然后int*您再次使用 编写float*,然后使用 a 将其读出float*,那么就可以了。

在非平凡分配器的情况下,您有一个大缓冲区,您通常将其存储在char*. 然后你进行某种指针算法来计算你想要分配的地址,然后通过分配器的头结构取消引用它。您使用什么指针来进行指针运算并不重要,只有您通过事务取消引用该区域的指针。由于在分配器中您总是通过分配器的标头结构执行此操作,因此您不会触发未定义的行为。

于 2015-11-28T17:52:44.187 回答
0

在分配器本身中,仅将内存缓冲区称为 (void *)。优化时,编译器不应应用严格别名优化(因为该模块不知道那里存储了哪些类型)。当该对象链接到系统的其余部分时,应该将其单独放置。

希望这可以帮助!

于 2011-10-07T12:22:52.987 回答