在下面的代码中,释放Dairy
也会释放Yogurt
吗
据我所知,他们都指向同一个地址。
此外,这种编码方式是不是一种糟糕的做法?假设我只保留指向Dairy
的指针,并间接释放Yogurt
和Cheese
?
#include <stdlib.h>
typedef struct {
int calcium;
int protein;
} Dairy;
typedef struct {
Dairy dairy;
int sugar;
int color;
} Yogurt;
int main () {
Yogurt* yogurt = malloc(sizeof(Yogurt));
Dairy* dairy = &yogurt->dairy;
free(dairy); // Will this free yogurt?
}
是的,行为定义良好。
根据C标准,您需要将与malloc
或其他分配函数返回的地址完全相同的地址传递给解除分配函数free
。
参考:
7.22.3.3免费功能
剧情简介:
§1 #include <stdlib.h> void free(void *ptr);
描述:
§2自由函数导致ptr指向的空间被释放,也就是说,使可供进一步分配。如果ptr是一个空指针,则不会发生任何操作。否则,如果参数与内存管理先前返回的指针不匹配函数,或者如果调用free或realloc已释放空间,则行为未定义。
因此,在这种情况下,当且仅当指向该结构的指针与指向其第一个成员的指针相同时,行为就可以得到很好的定义
这是由中的标准保证的
6.7.2.1结构和并集说明符
§15
在结构对象中,非位字段成员和位字段所在的单位resident的地址按声明的顺序增加指向经过适当转换的结构对象的指针指向其初始成员(或者,如果该成员是位字段,则指向其所在的单元),反之亦然。可能有未命名的在结构对象中填充,但不在其开头
这里有一个相关的讨论:这个代码有C标准保证吗?
它指的是C标准以及关于填充的定义。填充永远不会在结构的开头完成,因此代码可以工作。我甚至可以说,这是在许多C代码中发现的一种非常常见的模式。
指向结构的指针和指向结构的第一个成员的指针必须指向同一地址:
C99 6.7.2.11/13结构和并集说明符
指向结构对象的指针(经过适当转换)指向其初始成员(或者,如果该成员是位字段,则指向其所在的单元),反之亦然。结构对象中可能存在未命名的填充,但不在其开头。
所以,你可以free()
的任何一种表达方式,尽管我不推荐它的风格,因为我认为它对读者来说似乎相当脆弱和困惑。但我认为,如果你正在实现一种基于C对象的API,它期望有一个"基类"指针并能够释放它,那么它是有意义的(也是有用的)
它会的。free
获取一个void
指针,并将释放您告诉它释放的任何内容。由于这是C,所以与C++相比,不需要担心析构函数
请注意,这只是因为Dairy
是在"继承的"结构中首先声明的。如果更改申报顺序,这将不再有效。
编辑:仔细想想,你不能依赖这个。您不知道可能会发生什么编译器魔术,也不知道可能发生什么打包,从而偏移结构中的Dairy
成员。它可能会起作用,但我不确定你是否能保证它一直起作用。
可以肯定地说,这是一种糟糕的做法,以后可能会导致可移植性问题。尤其是对于那些以不同方式打包结构并使用不同的原生单词对齐的平台。它也可能被#pragma pack和__declspec(align())搞砸了
如果你想单独解放乳制品,你可以用乳制品*代替酸奶中的乳制品。不过,您还必须单独分配。