我正在做一些多态C代码,我想知道下面提出的两个内存释放函数是否能正常工作。我很确定void free_casted_parent(Parent* parent)
是正确的,但我开始想知道内部的专门化是否有必要?
typedef enum {
CHILD_TYPE_1,
CHILD_TYPE_2,
} Kind;
typedef struct {
Kind kind;
} Parent;
typedef struct {
Kind kind;
int value;
} ChildType1;
typedef struct {
Kind kind;
float other_value;
} ChildType2;
void free_parent(Parent* parent) {
free(parent);
}
void free_casted_parent(Parent* parent) {
switch (parent->kind) {
case CHILD_TYPE_1:
free((ChildType1*) parent);
break;
case CHILD_TYPE_2:
free((ChildType2*) parent);
break;
}
}
int main() {
ChildType1* ptr1 = malloc(sizeof(ChildType1));
ChildType2* ptr2 = malloc(sizeof(ChildType2));
// do some stuff with both variants of parent
free_parent((Parent*) ptr1);
free_casted_parent((Parent*) ptr2);
return 0;
}
如果free_casted_parent(Parent* parent)
不正确,那么这与释放堆分配的值数组有什么不同?在这两种情况下,我们都传递有效地址,而不指定确切的大小
2条答案
按热度按时间jobtbby31#
所示代码中的所有指针转换都是由C 2018定义的6.3.2.3 7:
指向对象类型的指针可以被转换为指向不同对象类型的指针。如果结果指针与引用的类型没有正确对齐,则行为未定义。否则,当再次转换回来时,结果将与原始指针进行比较。
由于指针被分配了
malloc
,它为任何基本类型提供了合适的内存地址,所以上面的第二句话不适用,并且定义了行为。也就是说,free
调用中的指针转换是无用的,因为对free
的调用会隐式地将参数转换为void *
,使free((ChildType1*) parent);
等效于free((void *) parent);
或free(parent);
。显示的引起未定义行为问题的唯一代码是
switch (parent->kind)
。(// do some stuff with both variants of parent
中没有显示的代码中可能有问题。)这使用Parent
类型访问内存,但是我们不知道内存的有效类型是什么,因为动态分配内存的有效类型取决于用于将值存储到内存的类型,并且该代码没有显示。假设使用了ChildType1
或ChildType2
,在这种情况下,这种访问可能违反了C 2018 6.5 7中的别名规则,因此具有未定义的行为。ajsxfq5m2#
free()
将正常工作,但过于复杂,而不是:任何对象指针都可以转换为
void *
,这是free()
操作的对象。在对
free_casted_parent()
的调用中,从两个不相关的类型ChildType1 *
或ChildType2 *
隐式转换为Parent *
是有问题的,根据6.5 7:一个对象的存储值只能由具有以下类型之一的左值表达式访问:
您缺少
#include <stdlib.h>
。malloc()
返回NULL(错误时),这将导致parent->
发生segfault。你要检查一下。您需要考虑两种替代设计:
1.使用常规标记的联合:
1.如果您不需要运行时多态行为,则可以省略标记并相应地处理每个类型(可能使用宏来生成其他相同的特定类型函数)。因为你可能有这些子元素的
n
,所以你保存n * size(Kind)
,并可能在struct
s中填充。如果你的子类型中有一个以上的成员,你会发现将数据存储为数组结构在运行时会更快,并且根据成员的类型,会导致更少的填充: