6

我收到了这条消息:

expected 'void **' but argument is of type 'char **'

当我试图编译类似这样的东西时:

void myfree( void **v )
{
    if( !v || !*v )
        return;

    free( *v );
    *v = NULL;

    return;
}



在阅读有关堆栈溢出的这个问题后,我发现了我认为的解决方案:
避免不兼容的指针警告在处理双重间接时 - 堆栈溢出

所以我调整为这样的内容:

#include <stdio.h>
#include <stdlib.h>

void myfree( void *x )
{
    void **v = x;

    if( !v || !*v )
        return;

    free( *v );
    *v = NULL;

    return;
}

int main( int argc, char *argv[] )
{
    char *test;

    if( ( test = malloc( 1 ) ) )
    {
        printf( "before: %p\n", test );
        myfree( &test );
        printf( "after: %p\n", test );
    }

    return 0;
}

这是合法的C吗?我正在取消引用一个 void 指针,不是吗?

谢谢大家


编辑 2010 年 12 月 10 日下午 4:45 EST:
正如已经指出的那样,它free(NULL)是安全的并且被 C 标准覆盖。此外,如下所述,我上面的示例是不合法的 C。请参阅 caf 的答案、Zack 的答案和我自己的答案。

因此,我将更容易将任何要被 malloc 的指针初始化为 NULL,然后在代码中直接将 free() 和 NULL 初始化为:

free( pointer );
pointer = NULL;

我像以前一样在 myfree() 中检查 NULL 的原因是因为我使用 fclose() 的经验。fclose(NULL)可以根据平台(例如 xpsp3 msvcrt.dll 7.0.2600.5512)发生段错误,所以我假设(错误地)同样的事情可能发生在 free() 上。我想,与其用 if 语句弄乱我的代码,我可以更好地在函数中实现。

谢谢大家的好讨论

4

4 回答 4

4

不,这不是合法的 C,除非您将void *对象的地址传递给myfree()(所以您最好保留原始定义)。

原因是在您的示例中,类型char *的对象(声明为testin的对象main())是通过类型void *的左值(中的左值*v)修改的myfree()。C 标准的 §6.5 规定:

7 对象的存储值只能由具有以下类型之一的左值表达式访问:

— a type compatible with the effective type of the object,
— a qualified version of a type compatible with the effective type of 
the object,
— a type that is the signed or unsigned type corresponding to the effective
type of the object,
— a type that is the signed or unsigned type corresponding to a qualified
version of the effective type of the object,
— an aggregate or union type that includes one of the aforementioned
types among its members (including, recursively, a member of a subaggregate
or contained union), or
— a character type.

由于void *char *是不兼容的类型,因此该约束已被打破。§6.7.5.1 中描述了两种指针类型兼容的条件:

对于要兼容的两种指针类型,两者都应具有相同的限定,并且都应是指向兼容类型的指针。

要达到你想要的效果,必须使用宏:

#define MYFREE(p) (free(p), (p) = NULL)

(不需要检查NULL,因为free(NULL)它是合法的。请注意,此宏会计算p两次)。

于 2010-12-06T22:12:25.437 回答
2

这是完全合法的,但可能会让其他阅读您的代码的人感到困惑。

您还可以使用强制转换来消除警告:

myfree((void **)&rest);

这更具可读性和可理解性。

于 2010-12-06T21:46:56.707 回答
1

在 C 语言中,你别无选择,只能在这里的某个地方引入演员表。我会使用一个宏来确保在呼叫站点正确地完成了事情:

void
myfree_(void **ptr)
{
    if (!ptr || !*ptr) return;
    free(*ptr);
    *ptr = 0;
}
#define myfree(ptr) myfree_((void **)&(ptr))

[由于 C 的无无限宏递归规则,您实际上可以将函数和宏命名为“myfree”!但这会让人类读者感到困惑。根据caf答案下面的长时间讨论,我还将规定*ptr = 0这里的语句通过别名修改未知类型的对象void**,这是运行时未定义的行为——但是,我的明智意见是,它不会在实践中引起问题,并且这是纯 C 语言中最不坏的选择;caf 的宏对其参数进行两次评估似乎(对我而言)更有可能导致真正的问题。]

在 C++ 中,您可以使用模板函数,这在三个方面更好:它避免需要在调用站点获取任何东西的地址,它不会破坏类型正确性,并且您将得到编译时错误而不是如果您不小心将非指针传递给myfree.

template <typename T>
void
myfree(T*& ptr)
{
    free((void *)ptr);
    ptr = 0;
}

但当然,在 C++ 中,您还有更好的选择,例如智能指针和容器类。

最后,应该提到的是,熟练的 C 程序员会避开这种包装器,因为当你刚刚释放的内存指针的另一个副本挂在某个地方时,它对你没有帮助——而这正是你需要帮助的时候。

于 2010-12-06T22:13:10.173 回答
1

caf 的回答是正确的:不,这不合法。正如 Zack 指出的那样,以这种方式违法显然是最不可能引起问题的。

我在comp.lang.c 常见问题列表·问题 4.9中发现了另一个解决方案,其中指出必须使用中间 void 值。

#include <stdio.h>
#include <stdlib.h>

void myfree( void **v )
{
    if( !v )
        return;

    free( *v );
    *v = NULL;

    return;
}

int main( int argc, char *argv[] )
{
    double *num;

    if( ( num = malloc( sizeof( double ) ) ) )
    {
        printf( "before: %p\n", num );

        {
            void *temp = num;
            myfree( &temp );
            num = temp;
        }

        printf( "after: %p\n", num );
    }

    return 0;
}


于 2010-12-10T21:36:14.987 回答