C语言是否有一种通用的“指向指针”的类型?

3
例如,如果我想编写一个“免费”的程序来清空指针,我可以写出以下代码:
void myfree(void **data) {
    free(*data);
    *data = NULL;
}

然而,当我尝试编写此代码时,我收到一个编译器警告(来自gcc 4.6.2),指出:warning: passing argument 1 of ‘myfree’ from incompatible pointer type [enabled by default] ... note: expected ‘void **’ but argument is of type ‘char **‘(在这种情况下,我正在释放一个char数组)。看起来void*被特殊处理以避免这种警告,因为callocfree等不会触发这样的警告,但是void**没有(根据上述内容)。唯一的解决方案是显式转换,还是我误解了什么?[我正在重新审视最近项目中的一些痛点,思考如何更好地处理它们,因此在探究边角案例,所以今天问了这些C语言问题。]更新:鉴于void*被特殊处理,我可以使用void*和在myfree内部进行强制转换来绕过这个问题,但这将是一种不负责任的解决方案,因为每个人都会传递一个类似于free的指针,因此我需要一种基于“间接程度”的编译器警告才能实现实际解决方案。因此,我想到了一个通用的“指向指针”的想法。

在传递时只需转换为 void** - Daniel Fischer
没错,但这很丑陋+混乱,特别是当free不需要它时。因此想知道是否有更好的方法。 - andrew cooke
顺便说一下,在我看来,你在释放指针时不应该经常将它们设置为null。有些情况下确实需要这样做,但也有些情况下你不能这样做(例如如果指针本身是“const”)。其他人认为总是设置为null非常重要,但我认为他们应该设计自己的代码,以便在第一时间就减少悬空指针的数量。大多数(不是全部)情况下,你会在释放包含指针的结构体之前或者在指针本身超出作用域之前释放指针。所以没有必要将其设置为null:它已经不存在了。 - Steve Jessop
2个回答

5

从技术上讲,标准允许不同的对象指针类型具有不同的表示形式(甚至不同的大小),尽管要求char*void*具有相同的表示形式。但以下情况是未定义行为:

int *ip = 0;
free(*(void**)(&ip));

仅仅因为ip的内存大小不需要与void*相同,即使它是int*类型的空指针位模式不需要与void*类型的空指针位模式相同。如果它们不同,那么编译器必须插入代码,在将int*转换为void*或反向转换时进行转换。在实践中,实现不会这样做(例如Posix禁止这样做)。更重要的是,严格别名规则不允许您使用void*类型的左值访问char*对象。因此,在实践中,有关指针表示的问题不会破坏您的代码,但优化器实际上可能会。基本上,如果函数调用myfree((void**)(&p))被内联,那么编译器可能会看到:
char *p = <something>;
void **data = (void**)(&p);
free(*data);
*data = NULL;
// code that reads p

优化器可以注意到*data = NULL;将一个void*类型的对象设置为NULL,而“读取p的代码”正在读取一个不允许与另一个void*对象别名的char*类型的对象。因此,它可以重新排序指令,完全消除*data = NULL;,或者可能是其他一些我没想到的东西,这些都会破坏你的日子,但如果你没有违反规则,这会加快代码速度。

UB?那是什么意思(抱歉)? - andrew cooke
未定义行为。这意味着标准不关心会发生什么。实现可以做任何他们喜欢的事情,通常他们所做的是意外的,因此不愉快和/或不一致。 - Steve Jessop
好的,谢谢,我需要考虑一下。现在对我来说还不清楚一些内容如何未定义,但是stdlib却能使用void*处理得很好。 - andrew cooke
1
您可以将任何对象指针类型转换为 void*,但这种转换可能需要时间和精力,并涉及更改位模式。因此,您不能只是像读取 void* 一样读取另一个指针类型。同样,您可以将 int 转换为 double,但您不能只是像读取 double 一样读取 int - Steve Jessop
严格别名规则的原因在于,即使没有别名发生,允许潜在别名也会真正减慢程序的运行速度。因此,允许编译器假设不存在别名的规则可以加快程序的运行速度。然而,这是一种权衡,因为有时您想要别名不同类型,但语言不允许。 - Steve Jessop

-1

您可以使用宏来执行此操作。与使用函数相比,这将非常方便;我希望您知道使用宏的优势。

#define FREE_IF_NOT_NULL(x) if (x != NULL) { \
                                            free(x); \
                                            x = NULL; \
                                       }

3
free()是空安全的——if是多余的;等价的定义是((void)(free(x), x = NULL)) - Christoph
2
我希望你知道使用宏的缺点。 - gliderkite

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接