[英]C memory allocator and strict aliasing
即使在阅读了相当多的严格别名规则后,我仍然感到困惑。 据我所知,不可能实现遵循这些规则的合理的内存分配器,因为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...
}
我不认为你是对的。 即使是最严格的严格别名规则也只会在实际为某个目的分配内存时计算。 一旦分配的块已被释放回用堆free
,应该对它的引用,它可以通过再次给出了malloc
。
并且malloc
返回的void*
不受严格别名规则的约束,因为标准明确声明可以将void指针强制转换为任何其他类型的指针(并再次返回)。 C99第7.20.3节规定:
如果分配成功,则返回的指针被适当地对齐,以便可以将其指定给指向任何类型对象的指针,然后用于在分配的空间中访问此类对象或此类对象的数组(直到空间被显式释放) 。
就你没有实际将内存返回堆的更新(例子)而言,我认为你的混乱是因为分配的对象被特别处理。 如果您参考C99的6.5/6
,您会看到:
用于访问其存储值的对象的有效类型是对象的声明类型(如果有)(脚注75:已分配的对象没有声明的类型)。
重新阅读该脚注,这很重要。
如果通过具有非字符类型的左值的值将值存储到没有声明类型的对象中,则左值的类型将成为该访问的对象的有效类型以及不修改该值的后续访问的有效类型储值。
如果使用memcpy或memmove将值复制到没有声明类型的对象中,或者将其复制为字符类型数组,则该访问的修改对象的有效类型以及不修改该值的后续访问的有效类型是复制值的对象的有效类型(如果有)。
对于没有声明类型的对象的所有其他访问,对象的有效类型只是用于访问的左值的类型。
换句话说,分配的块内容将成为您放在那里的数据项的类型。
如果你在那里放一个float
,你应该只将它作为float
(或兼容类型)访问。 如果放入int
,则只应将其作为int
(或兼容类型)处理。
您不应该做的一件事是将特定类型的变量放入该内存中,然后尝试将其视为不同的类型 - 这是因为允许对象具有陷阱表示(导致未定义的行为)和由于将同一对象视为不同类型,可能会出现这些表示。
所以,如果你要存储一个int
在那里在代码中释放之前,然后重新分配它作为一个float
指针,你不应该尝试使用浮动,直到你居然把一个在那里。 到那时为止,分配的类型还没有float
。
我发布这个答案来测试我对严格别名的理解:
严格别名仅在实际读取和写入时才有意义。 正如同时使用不同类型的并集的多个成员是未定义的行为一样,同样适用于指针:您不能使用不同类型的指针来访问相同的内存,原因与您无法使用union一样。
如果您只考虑其中一个指针,那么这不是问题。
int*
编写并通读int*
,则可以。 int*
编写并通过float*
读取,则很糟糕。 int*
以后你写再次使用float*
,然后读出来使用float*
,那么它的确定。 对于非平凡的分配器,您有一个大缓冲区,通常将其存储在char*
。 然后你做一些指针算术来计算你想要分配的地址,然后通过分配器的头部结构取消引用它。 你使用什么指针来做指针算术并不重要只有你通过问题取消引用区域的指针。 因为在分配器中,您总是通过分配器的头结构执行此操作,因此您不会触发未定义的行为。
标准C没有定义任何有效的方法,通过这种方法,用户编写的内存分配器可以安全地获取已被用作一种类型的内存区域,并使其安全地可用作另一种类型。 C中的结构保证不会陷阱表示 - 如果它不能使用包含Indeterminate Value的字段复制结构的安全性,那么它就没有什么用处。
困难在于给定一个结构和功能,如:
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
将保证它返回的分配块可以被任何类型使用,但是用户编写的内存管理功能无法实现存储的重用,而无需以字节方式(使用循环或memset)清除它或否则使用free()和malloc()来回收存储。
在分配器本身内,只将内存缓冲区称为(void *)。 当它被优化时,编译器不应该应用严格别名优化(因为该模块不知道那里存储了什么类型)。 当该对象链接到系统的其余部分时,它应该留下足够的单独。
希望这可以帮助!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.