C 为什么这种声称的取消引用类型双关指针警告特定于编译器?
我读过关于:去费林类型双关指针错误。我的理解是,该错误本质上是编译器警告通过不同类型的指针访问对象的危险(尽管C 为什么这种声称的取消引用类型双关指针警告特定于编译器?,c,pointers,casting,C,Pointers,Casting,我读过关于:去费林类型双关指针错误。我的理解是,该错误本质上是编译器警告通过不同类型的指针访问对象的危险(尽管char*似乎有例外),这是一个可以理解和合理的警告 我的问题针对以下代码:为什么将指针地址强制转换为无效**符合此警告条件(通过-Werror升级为错误)? 此外,此代码针对多个目标体系结构进行编译,其中只有一个生成警告/错误-这是否意味着这是编译器版本特定的缺陷? // main.c #include <stdlib.h> typedef struct Foo {
char*
似乎有例外),这是一个可以理解和合理的警告
我的问题针对以下代码:为什么将指针地址强制转换为无效**
符合此警告条件(通过-Werror
升级为错误)?
此外,此代码针对多个目标体系结构进行编译,其中只有一个生成警告/错误-这是否意味着这是编译器版本特定的缺陷?
// main.c
#include <stdlib.h>
typedef struct Foo
{
int i;
} Foo;
void freeFunc( void** obj )
{
if ( obj && * obj )
{
free( *obj );
*obj = NULL;
}
}
int main( int argc, char* argv[] )
{
Foo* f = calloc( 1, sizeof( Foo ) );
freeFunc( (void**)(&f) );
return 0;
}
问题编译器(其中一个):
不抱怨编译器(众多编译器之一):
更新:我进一步发现警告似乎是在使用
-O2
编译时生成的(仍然只使用注意到的“问题编译器”)取消引用类型双关指针是UB,您不能指望会发生什么
不同的编译器生成不同的警告,为此,同一编译器的不同版本可以视为不同的编译器。这似乎比依赖于体系结构更好地解释了您所看到的差异
有一种情况可以帮助您理解为什么在这种情况下类型双关可能是不好的,那就是您的函数在sizeof(Foo*)!=sizeof(void*)
。这是标准授权的,尽管我不知道当前任何一个标准是正确的
解决方法是使用宏而不是函数
请注意,
free
接受空指针。类型为void**
的值是指向类型为void*
的对象的指针。Foo*
类型的对象不是void*
类型的对象
类型为Foo*
和void*
的值之间存在隐式转换。此转换可能会更改值的表示形式。类似地,您可以编写intn=3;双x=n
这具有定义良好的行为,将x
设置为3.0
,但是double*p=(double*)&n
具有未定义的行为(实际上,在任何通用体系结构上都不会将p
设置为“指向3.0
”的指针)
如今,指向对象的不同类型指针具有不同表示形式的体系结构已经很少见了,但C标准允许这样做。有(罕见的)旧机器,其字指针是内存中某个字的地址,字节指针是该字的地址以及该字的字节偏移量Foo*
将是此类体系结构上的单词指针,void*
将是字节指针。有(罕见的)带有胖指针的机器,它们不仅包含有关对象地址的信息,还包含关于对象类型、大小和访问控制列表的信息;指向特定类型的指针可能与运行时需要附加类型信息的void*
具有不同的表示形式
这种机器很少见,但C标准允许。一些C编译器利用权限将类型双关指针视为不同的指针来优化代码。指针别名的风险是编译器优化代码能力的主要限制,因此编译器倾向于利用这种权限
编译器可以自由地告诉你你做错了什么,或者悄悄地做你不想做的事,或者悄悄地做你想做的事。未定义的行为允许任何这些
您可以将freefunc
设置为宏:
#define FREE_SINGLE_REFERENCE(p) (free(p), (p) = NULL)
这与宏的常见限制一起出现:缺乏类型安全性,
p
被评估两次。请注意,如果p
是指向已释放对象的单个指针,则这只会为您提供不留下悬空指针的安全性。Avoid*
在某种程度上受到C标准的特殊处理,因为它引用了不完整的类型。这种处理方式不会扩展到void**
,因为它确实指向一个完整的类型,特别是void*
严格的别名规则规定,不能将一种类型的指针转换为另一种类型的指针,然后取消对该指针的引用,因为这样做意味着将一种类型的字节重新解释为另一种类型。唯一的例外是转换为允许读取对象表示的字符类型时
您可以通过使用类似宏的函数而不是函数来绕过此限制:
#define freeFunc(obj) (free(obj), (obj) = NULL)
你可以这样称呼它:
freeFunc(f);
但是,这有一个限制,因为上面的宏将计算两次obj
。如果您使用的是GCC,可以通过一些扩展来避免这种情况,特别是typeof
关键字和语句表达式:
#define freeFunc(obj) ({ typeof (&(obj)) ptr = &(obj); free(*ptr); *ptr = NULL; })
根据C标准,此代码无效,因此在某些情况下可能有效,但不一定是可移植的 6.5第7段中给出了通过已转换为不同指针类型的指针访问值的“严格别名规则”: 对象的存储值只能由具有以下类型之一的左值表达式访问:
- 与对象的有效类型兼容的类型
- 与对象的有效类型兼容的类型的限定版本
- 与对象的有效类型相对应的有符号或无符号类型
- 一种类型,它是与对象的有效类型的限定版本相对应的有符号或无符号类型
- 一种聚合或联合类型,其成员(包括
#define freeFunc(obj) (free(obj), (obj) = NULL)
freeFunc(f);
#define freeFunc(obj) ({ typeof (&(obj)) ptr = &(obj); free(*ptr); *ptr = NULL; })