在C中释放向上转换的结构,它有多安全?

z9ju0rcb  于 2023-10-16  发布在  其他
关注(0)|答案(2)|浏览(80)

我正在做一些多态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)不正确,那么这与释放堆分配的值数组有什么不同?在这两种情况下,我们都传递有效地址,而不指定确切的大小

jobtbby3

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类型访问内存,但是我们不知道内存的有效类型是什么,因为动态分配内存的有效类型取决于用于将值存储到内存的类型,并且该代码没有显示。假设使用了ChildType1ChildType2,在这种情况下,这种访问可能违反了C 2018 6.5 7中的别名规则,因此具有未定义的行为。

ajsxfq5m

ajsxfq5m2#

free()将正常工作,但过于复杂,而不是:

free(ptr1);
free(ptr2);

任何对象指针都可以转换为void *,这是free()操作的对象。
在对free_casted_parent()的调用中,从两个不相关的类型ChildType1 *ChildType2 *隐式转换为Parent *是有问题的,根据6.5 7:
一个对象的存储值只能由具有以下类型之一的左值表达式访问:

  • 与对象的有效类型兼容的类型[...]
    您缺少#include <stdlib.h>
    malloc()返回NULL(错误时),这将导致parent->发生segfault。你要检查一下。
    您需要考虑两种替代设计:
    1.使用常规标记的联合:
typedef struct  {
  Kind kind;
  union {
    int value;
    float other_value;
  };
} ChildType;

1.如果您不需要运行时多态行为,则可以省略标记并相应地处理每个类型(可能使用宏来生成其他相同的特定类型函数)。因为你可能有这些子元素的n,所以你保存n * size(Kind),并可能在struct s中填充。

typedef struct {
    int value;
} ChildType1;

typedef struct {
    float value;
} ChildType2;

如果你的子类型中有一个以上的成员,你会发现将数据存储为数组结构在运行时会更快,并且根据成员的类型,会导致更少的填充:

typedef struct {
   int *values;
} ChildType1;

// ...

ChildType1 childtype1;
childtype1.values = malloc((sizeof *childtype1.values) * 42)

相关问题