我试图在这里阅读其他类似 Headers 的问题,但是对于我来说,能够将解决方案(甚至解释)应用到我自己的问题上看起来有点过于复杂,这似乎是更简单的性质 .
在我的情况下,我有一个 free()
的包装器,它在释放后将指针设置为 NULL
:
void myfree(void **ptr)
{
free(*ptr);
*ptr = NULL;
}
在我正在研究的项目中,它被称为:
myfree((void **)&a);
这使 gcc
(OpenBSD上的4.2.1)发出警告"dereferencing type-punned pointer will break strict-aliasing rules"如果我将优化级别调高到 -O3
并添加 -Wall
(不是其他) .
以下方式调用 myfree()
不会使编译器发出该警告:
myfree((void *)&a);
所以我想知道我们是否应该改变我们称之为 myfree()
的方式 .
我相信我正在调用 myfree()
的第一种方式调用未定义的行为,但我无法绕过原因 . 此外,在我有权访问的所有编译器( clang
和 gcc
)上,在所有系统(OpenBSD,Mac OS X和Linux)上,这是唯一实际上给我警告的编译器和系统(我知道发出的警告是一个很好的可选) .
在调用 myfree()
之前,之内和之后打印指针的值,使用两种方式调用它,给我相同的结果(但如果它是未定义的行为,则可能没有任何意义):
#include <stdio.h>
#include <stdlib.h>
void myfree(void **ptr)
{
printf("(in myfree) ptr = %p\n", *ptr);
free(*ptr);
*ptr = NULL;
}
int main(void)
{
int *a, *b;
a = malloc(100 * sizeof *a);
b = malloc(100 * sizeof *b);
printf("(before myfree) a = %p\n", (void *)a);
printf("(before myfree) b = %p\n", (void *)b);
myfree((void **)&a); /* line 21 */
myfree((void *)&b);
printf("(after myfree) a = %p\n", (void *)a);
printf("(after myfree) b = %p\n", (void *)b);
return EXIT_SUCCESS;
}
编译并运行它:
$ cc -O3 -Wall free-test.c
free-test.c: In function 'main':
free-test.c:21: warning: dereferencing type-punned pointer will break strict-aliasing rules
$ ./a.out
(before myfree) a = 0x15f8fcf1d600
(before myfree) b = 0x15f876b27200
(in myfree) ptr = 0x15f8fcf1d600
(in myfree) ptr = 0x15f876b27200
(after myfree) a = 0x0
(after myfree) b = 0x0
我想了解第一次调用 myfree()
时出了什么问题,我想知道第二次调用是否正确 . 谢谢 .
3 回答
由于
a
是int*
而不是void*
,因此&a
无法转换为指向void*
的指针 . (假设void*
比指向整数的指针更宽,这是C标准允许的 . )因此,您的备选方案 -myfree((void**)a)
和myfree((void*)a)
都不正确 . (转换为void*
不是严格的别名问题 . 但它仍会导致未定义的行为 . )更好的解决方案(imho)是强制用户插入可见的赋值:
使用clang和gcc,您可以使用一个属性来指示必须使用
my_free
的返回值,这样编译器会在您忘记赋值时发出警告 . 或者您可以使用宏:这是一个建议:
不违反严格别名规则 .
使通话更自然 .
您可以将宏简单地用作:
基本上有几种方法可以使函数与指针的目标类型无关地修改指针和修改指针:
将指针作为void *传递给函数,并将其作为void *返回,在调用站点的两个方向上应用适当的转换 . 这种方法的缺点是绑定了函数的返回值,妨碍了其用于其他目的,并且还排除了在锁内执行指针更新的可能性 .
传递指向函数的指针,该函数接受两个void *,将其中一个转换为适当类型的指针,另一个转换为该类型的双间接指针,并且可能是第二个可以读取传入指针的函数void *,并使用这些函数来读取和写入有问题的指针 . 这应该是100%便携式,但可能非常低效 .
在其他地方使用指针变量和void *类型的字段,并在它们实际使用时将它们转换为实数指针类型,从而允许使用void **类型的指针来修改指针变量 .
使用
memcpy
读取或修改未知类型的指针,给定识别它们的双间接指针 .文档代码是用20年代流行的C语言编写的,它将"void**"视为任何类型的双间接指针,并使用支持该方言的编译器和/或设置 . C标准允许实现对不同类型的事物的指针使用不同的表示,并且因为这些实现不能支持通用的双间接指针类型,并且因为可以轻松地允许
void**
的实现已经这样做了在编写标准之前,没有人认为需要标准来描述这种行为 .具有通用双间接指针类型的能力在90%的实现中非常有用可以(并且确实)很乐意支持它,标准的作者肯定知道这一点,但作者对描述敏感的编译器编写者无论如何会支持的行为的兴趣远远低于强制行为,即使对整体有益的行为也是如此 . 它们不能被廉价支持的平台(例如,即使在无符号数学指令包装mod 65535的平台上,编译器也必须生成需要的代码才能使计算包装为mod 65536) . 我不确定为什么现代编译器编写者无法识别它 .
也许如果程序员开始公开写出C的理智方言,标准的维护者可能会认识到这样的方言有 Value . [请注意,从别名的角度来看,将
void**
视为通用双间接指针将比强制程序员使用上述任何替代方案2-4的性能成本要低得多;编译器编写者声称将void**
视为通用双间接指针会导致性能下降的任何声明因此应该持怀疑态度 .