C 语言中释放函数的典型原型是什么?

What is the typical prototype for a deallocation function in C?

提问人:IPribec 提问时间:1/30/2023 最后编辑:Vlad from MoscowIPribec 更新时间:1/30/2023 访问量:120

问:

查看 StackOverflow 上示例上的代码,我注意到对象释放有两个不同的原型:

struct foo *foo_create(int);
void foo_free_v1(struct foo *);
void foo_free_v2(struct foo **);

void bar() {

    struct *foo = foo_create(7);

    // ...

    // Version 1
    foo_free_v1(foo);

    // Version 2
    foo_free_v2(&foo);
}

标准库函数使用第一种形式。free

第二种形式是 C 中的惯用语吗?如果是,出于什么目的?

C 结构 按引用 值传递函数 声明

评论

0赞 tstanisl 1/30/2023
这将使使用合格的指针变得非常麻烦。foo_free_v2struct foo

答:

2赞 Alexander 1/30/2023 #1

那些采用指针到指针的那些这样做是因为它们具有额外的便利性,可以自动为您清空您的变量。

它们可能如下所示:

void foo_free_v1(struct foo *f) {
    if (f == NULL) return; // This has been freed before, don't do it again!

    free(f->a);
    free(f->b);
    free(f->c);
    free(f);
}
void foo_free_v2(struct foo **f) {
    if (*f == NULL) return; // This has been freed before, don't do it again!

    free((*f)->a);
    free((*f)->b);
    free((*f)->c);
    free(*f);

    *f = NULL; // Null out the variable so it can't be freed again.
}

这试图防止双重释放错误。这是多么好的想法,值得商榷。请参阅下面的评论线程。

评论

1赞 Alexander 1/30/2023
@IPribec是的。如果没有,我真的不确定他们为什么想要双指针。除非他们释放了一个充满多个的缓冲区,但如果这是他们的意图,他们还需要取一个参数。struct foossize_t foo_count
2赞 Eric Postpischil 1/30/2023
回复“这可以防止双重释放错误”:嗯,它尝试了。如果代码包含同一指针的第二个空闲时间,则该代码通常是在指针仍指向分配空间的预期中编写的,这意味着第二个空闲时间之前的代码可能正在使用指针,因此代码可能会覆盖其他内容所需的数据,或者可能正在读取不再包含预期数据的内存。通过抑制双重释放(可能会立即报告错误),这允许错误更长时间未被发现,从而导致更多更难发现的问题。free
2赞 Eric Postpischil 1/30/2023
@Alexander:如果程序跟踪它已分配的内存,并且仅使用指向当前分配的内存的指针进行调用,则无需将该指针设置为 null。该地址将永远不会再传递给。如果有人在调用后将指针设置为 null,则两件事之一就是树。要么程序永远不会将相同的地址传递给任何地址,因此将指针设置为 null 不会有任何作用;它过于谨慎和浪费......freefreefreefree
1赞 Support Ukraine 1/30/2023
“这有助于防止双重释放错误。” 嗯......不确定我能同意。IMO 它有助于隐藏错误。
1赞 Avi Berger 1/30/2023
释放后使用和尝试释放:这两个错误,都令人讨厌。也不想要。在这场辩论之外,释放后 null 模型的用途可以用于可选处理 - 测试 null 并在存在对象时进行(不同的)处理。我并不是说这很常见,但这是一个用例,超出了“哪个更容易捕获错误”的争论。
0赞 Vlad from Moscow 1/30/2023 #2

在第一个函数中

void foo_free_v1(struct foo *);

用作参数表达式的原始指针将按值传递给函数。这意味着该函数处理传递给该函数的指针值的副本。

更改副本不会影响用作参数表达式的原始指针。

例如,考虑函数定义

void foo_free_v1(struct foo *p)
{
    free( p );
    p = NULL;
}

函数中的此语句

p = NULL;

不会将原始指针更改为 。它设置为函数局部变量。因此,在调用函数后用作参数表达式的指针将具有无效值。该值不指向现有对象。NULLNULLp

在这样声明的第二个函数中

void foo_free_v2(struct foo **);

用作参数表达式的原始指针通过指向它的指针被引用接受。因此,取消引用指针后,您可以直接访问用作参数表达式的原始指针。

考虑一个可能的函数定义

void foo_free_v2(struct foo **p)
{
    free( *p );
    *p = NULL;
}

在这种情况下,在语句中

*p = NULL;

它是设置为 的原始指针。因此,原始指针没有无效值。NULL

例如,考虑一个唱歌的链表,如

struct SinglyLinkedList
{
    int data;
     SinglyLinkedList *next;
};

在 main 中,您可以声明列表,例如

struct SinglyLinkedList *head = NULL;

然后,您可以将新节点添加到列表中。初始化指针时,添加新节点的函数将正常工作。head

之后,您可以销毁列表。

如果要调用声明如下的第一个函数

void foo_free_v1(struct SinglyLinkedList *head );

然后,在调用函数后,在 main 中声明的指针将具有无效值。也就是说,会有不一致的地方。该列表没有 already 元素,但其指向 head ode 的指针不等于 。headNULL

是你调用声明的函数 lik

void foo_free_v1(struct SinglyLinkedList **head );

那么实际上,指向 main 中头节点的指针将等于 NULL,这确实意味着列表是空的。如果您有一个检查列表是否为空的函数,则可以将指针传递给该函数,而不会产生未定义的行为。

1赞 chux - Reinstate Monica 1/30/2023 #3

C 语言中释放函数的典型原型是什么?

传递指针。如果非 - ,则不返回任何内容或很少使用任何内容。void

void foo_free_v1(foo_type *);

第二种形式()是C语言中的惯用语吗?foo_free_v2(&foo);

不。

经典示例:foo_free(foo_type *)

foo *f = malloc(sizeof *f);
...
free(f);

FILE *istream = fopen(...);         
int count = fscanf(istream, ...);
long offset = ftell(istream, ...);
fclose(istream);

何时使用 foo_free_v2(&foo);

Use when 不是指针类型。foo_free_v2(&foo)foo

例:

typedef struct {
  int object1;
  int *object2;
  int *object3;
  ... 
} foo_type;

foo_type foo = { 0 }; 

foo_get_resources(&foo, ...);
foo_do_this(&foo, ...);
foo_do_that(&foo, ...);
foo_free(&foo);