C问题:单个取消引用void **双间接指针

时间:2010-12-06 21:42:52

标签: c type-conversion compiler-warnings void void-pointers

我收到了这条消息:

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

当我尝试编译类似于此的东西时:

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

    free( *v );
    *v = NULL;

    return;
}



在堆栈溢出后阅读这个问题后,我发现了我认为的解决方案:
Avoid incompatible pointer warning when dealing with double-indirection - Stack Overflow

所以我调整了这样的事情:

#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吗?我解除引用无效指针不是吗?

谢谢你们

编辑12/10/2010美国东部时间下午4:45:
正如已经指出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 个答案:

答案 0 :(得分:4)

不,这是合法C,除非您将void *对象的地址传递给myfree()(因此您可能只保留原始定义)。

原因是在您的示例中,char *类型的对象(test中声明为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两次。

答案 1 :(得分:2)

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

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

myfree((void **)&rest);

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

答案 2 :(得分:1)

在C中你别无选择,只能在这里介绍一个演员阵容。我会使用宏来确保在呼叫站点正确完成事情:

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

[你可以实际命名函数和宏“myfree”,这要归功于C的无限宏递归规则!但这对人类读者来说会让人感到困惑。根据caf的回答下面的长篇讨论,我还将规定语句*ptr = 0在这里通过void**别名修改未知类型的对象,这是运行时未定义的行为 - 但是,我的知情意见是,它不会在实践中引起问题,而且它是普通C中可用性最差的选项;在两次评估其论点的caf的宏看起来更有可能(对我而言)引起真正的问题。]

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

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

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

最后,应该提到熟练的C程序员避开这种包装器,因为当你指向你刚刚释放的内存的指针的另一个副本时,它不会帮助你 - 这正是你需要的时候帮助

答案 3 :(得分:1)

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

我发现comp.lang.c FAQ list · Question 4.9中似乎是另一种解决方案,它指出必须使用中间空值。

#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;
}