简体   繁体   English

使用void *,void **和可变参数函数

[英]Use of void*, void ** and variadic function

I want to write a function that frees as many pointers as wanted. 我想编写一个可以释放尽可能多的指针的函数。 So I have this one : 所以我有这个:

void    myfree(size_t n, ...)
{
    void    **del;
    va_list ap;

    va_start(ap, n);
    while (n > 0)
    {
        del = va_arg(ap, void **);
        free(*del);
        *del = NULL;
        --n;
    }
    va_end(ap);
}

I call it like that : 我称之为:

char *one = malloc(x);
sometype_t *two = malloc(x);
myfree(2, &one, &two);

I assume that one and two are now pointing to NULL. 我假设onetwo现在指向NULL。 It seems to be working, but I'm still a bit worried. 它似乎有效,但我仍然有点担心。 I did some researches about void** but I'm not sure if my function is really valid (UB are you there ?) 我做了一些关于void**研究,但我不确定我的功能是否真的有效(UB你在那里吗?)


During my researches and tests, I tried some things I do not really understand. 在我的研究和测试中,我尝试了一些我不太懂的东西。

Let's say we have this function 假设我们有这个功能

void    f(void **ptr)
{
} 

If I call it like this 如果我这样称呼它

int *intptr = NULL;
f(&intptr);

I get a compiler warning : warning: incompatible pointer types passing 'int **' to parameter of type 'void **' 我得到一个编译器警告: warning: incompatible pointer types passing 'int **' to parameter of type 'void **'

So I tried this 所以我尝试了这个

int *intptr = NULL;
f( & ((void *)intptr) );

Compiler error : error: cannot take the address of an rvalue of type 'void *' 编译错误: error: cannot take the address of an rvalue of type 'void *'

But if I do 但如果我这样做

void *voidptr = NULL;
f(&voidptr); // I can take the address of a void pointer right ?

Actually void* , void** and conversions/casts with them are still unclear to me. 实际上,对我来说, void*void**和转换/转换仍然不清楚。 That is why I'm worried about myfree() I posted above. 这就是为什么我担心我在上面发布的myfree()。 If any of you have some explanations I would be grateful :) 如果你们有任何解释我会很感激:)

The C standard allows conversions to and from a void * , but not a void ** . C标准允许转换为void * ,而不是void ** On most implementation you're likely to come across they should be the same size, but there's no guarantee of that. 在大多数实现中,您可能会遇到它们应该具有相同的大小,但不能保证这一点。 So this is undefined behavior. 所以这是未定义的行为。

This is one of those cases when a macro function makes the most sense, although you'll only be able to free one pointer at a time: 这是宏功能最有意义的情况之一,尽管你一次只能释放一个指针:

#define myfree(ptr) do { free(ptr); ptr = NULL; } while (0)

as @dbush said in his answer your code has UB. 正如@dbush在他的回答中所说,你的代码有UB。 If you want to keep your function then you can pass the pointer directly, free the memory and not modify the pointer. 如果你想保留你的功能,那么你可以直接传递指针,释放内存而不是修改指针。 The setting to NULL is mostly useless anyway. 无论如何,设置为NULL几乎是无用的。

void myfree(size_t n, ...)
{
    void* del;
    va_list ap;

    va_start(ap, n);
    while (n > 0)
    {
        del = va_arg(ap, void *);
        free(del);
        --n;
    }
    va_end(ap);
}
char *one = malloc(x);
sometype_t *two = malloc(x);
myfree(2, one, two);

First half code is likely OK yet may invoke undefined behavior (UB). 上半部分代码很可能尚未调用未定义的行为 (UB)。


In calling myfree(2, &one, &two); 呼叫 myfree(2, &one, &two); , there are no conversions to void * nor void ** . ,没有转换void *void ** The pointers &one, &two are passed unconverted as char ** , sometype_t ** . 指针&one, &two未转换为char **sometype_t **

It is the implementation of myfree() that assumes the pointers are a compatible type to void ** when it does: 它是myfree()的实现,它假定指针是void **的兼容类型void **当它执行时:

void ** del = va_arg(ap, void **);`

Salient specification: 突出规格:

The va_arg macro expands .... If ... type is not compatible with the type of the actual next argument ..., the behavior is undefined C11dr §7.16.1.1 2 va_arg宏扩展....如果...类型与实际的下一个参数的类型不兼容...,行为是未定义的C11dr§7.16.1.12

The trick is that a pointer of void ** may not be compatible with a char ** , nor sometype_t ** . 诀窍是void **的指针可能与char **兼容 ,也不与sometype_t ** 兼容 Since no explicit conversion is coded, this is only OK if those pointer types are compatible . 由于没有编译显式转换,因此只有那些指针类型兼容时才可以。 Although this is very likely on the many architectures, it is not specified and so UB. 虽然这很可能在许多架构上,但它没有指定,所以UB。


With OP's 2nd half code, the conversion is done directly and the compiler warns: 使用OP的下半部分代码,转换将直接完成,编译器会发出警告:

I get a compiler warning : warning: incompatible pointer types passing 'int **' to parameter of type 'void **' 我得到一个编译器警告:警告:不兼容的指针类型将'int **'传递给'void **'类型的参数

In this case the conversion is coded as part of f(&intptr); 在这种情况下,转换被编码为f(&intptr); . §7.16.1.1 2 does not apply here. §7.16.1.12不适用于此处。 The specification of note: 注意事项说明:

A pointer to an object type may be converted to a pointer to a different object type. 指向对象类型的指针可以转换为指向不同对象类型的指针。 If the resulting pointer is not correctly aligned for the referenced type, the behavior is undefined. 如果生成的指针未针对引用的类型正确对齐,则行为未定义。 C11dr §6.3.2.3 7 C11dr§6.3.2.37

With decades of experience, I've never came across nor heard of a system that had pointers to pointers of different alignment. 凭借数十年的经验,我从未见过或听说过一个指向不同路线指针的系统。 So this is low risk UB, yet I would heed the warning and code otherwise. 所以这是低风险的UB,但我会注意警告和代码。


Alternative 替代

Pass and expect to receive void * pointers. 通过并期望收到void *指针。 Note that this does not set the free'd pointer to NULL . 请注意,这不会将free'd指针设置为NULL

void myfree(size_t n, ...) {
  void *del;  // one *
  va_list ap;

  va_start(ap, n);
  while (n > 0) {
     del = va_arg(ap, void *);
     free(del);
     --n;
    }
  va_end(ap);
}

Character pointers are compatible with void* and do not require a cast. 字符指针与void*兼容,不需要强制转换。 Arbitrary object pointers need a cast. 任意对象指针需要强制转换。 This is similar to the need for the cast in printf("%p\\n", (void*) two); 这类似于printf("%p\\n", (void*) two);中的printf("%p\\n", (void*) two);

char *one = malloc(x);
sometype_t *two = malloc(x);
void *three = malloc(x);
myfree(3, one, (void*) two, three);

Alternative 2 备选方案2

I want to write a function that frees as many pointers as wanted. 我想编写一个可以释放尽可能多的指针的函数。

Maybe instead of myfree(n, ...) , how about coding some of the more popular sizes, even as a macro, and leave it go at that? 也许不是myfree(n, ...) ,如何编写一些更流行的尺寸,甚至作为一个宏,并留下它? This certainly meets the majority of coding needs and, of course, avoids a myfree(3, p1, p2) error. 这当然满足了大多数编码需求,当然也避免了myfree(3, p1, p2)错误。 Recall that is is OK to call free4(p1, p2,p3,0); 回想一下,可以调用free4(p1, p2,p3,0);

free2(void *p1, void *p2)`
free3(void *p1, void *p2, void *p3)
free4(void *p1, void *p2, void *p3, void *p4)
// maybe a few others

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM