C内存分配器和严格别名

即使在阅读了很多关于严格别名规则之后,我仍然感到困惑。 据我所知,不可能实现遵循这些规则的合理的内存分配器,因为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... } 

我不认为你是对的。 即使是最严格的严格别名规则也只会在实际为某个目的分配内存时进行计数。 一旦已分配的块已释放回堆中,则应该没有对它的引用,并且它可以由malloc再次发出。

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

如果分配成功,则返回指针,以便可以将其指定给指向任何类型对象的指针,然后用于在分配的空间中访问此类对象或此类对象的数组(直到空间被明确释放) 。


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

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

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

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

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

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

换句话说,分配的块内容将成为您放在那里的数据项的类型。

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

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

所以,如果你要在代码中释放之前在那里存储一个int ,然后将它重新分配为float指针,你不应该尝试使用浮点数,直到你实际上把它放在那里。 到那时为止,分配的类型还没有float

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

严格别名仅在实际读写发生时才有意义。 正如同时使用不同类型的union的多个成员是未定义的行为一样,同样适用于指针:您不能使用不同类型的指针来访问相同的内存,原因与您无法使用union一样。

如果你只考虑其中一个指针,那么这不是问题。

  • 因此,如果您通过int*编写并通读int* ,则可以。
  • 如果使用int*编写并通过float*读取,那就很糟糕了。
  • 如果使用int*编写,稍后使用float*再次编写,则使用float*将其读出,然后就可以了。

对于非平凡的分配器,您有一个大缓冲区,通常将其存储在char* 。 然后你做一些指针算术来计算你想要分配的地址,然后通过分配器的头部结构取消引用它。 你使用什么指针来做指针算术并不重要只有你通过问题取消引用区域的指针。 因为在分配器中,您总是通过分配器的头结构执行此操作,因此您不会触发未定义的行为。

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

困难在于给定一个结构和function,如:

 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); 

无需先写入已分配结构的所有字段。 尽管malloc将保证它返回的分配块可以被任何类型使用,但是用户编写的内存管理function无法实现存储的重用,而无需以字节方式(使用循环或memset)清除它或否则使用free()和malloc()来回收存储。

在分配器本身内,只将内存缓冲区称为(void *)。 当它被优化时,编译器不应该应用严格别名优化(因为该模块不知道那里存储了什么类型)。 当该对象链接到系统的其余部分时,它应该足够好。

希望这可以帮助!