我已经阅读了 有关Stack Overflow RE的各种文章 :取消引用类型标记的指针错误。我的理解是,该错误本质上是编译器警告,它警告通过不同类型的指针访问对象的危险(尽管似乎为产生了例外),这是可以理解且合理的警告。 char*
我的问题特定于以下代码:为什么将指针的地址强制转换void**
为此警告的合格字符(通过导致错误-Werror
)?
而且,此代码针对多个目标体系结构进行了编译,只有其中一种会生成警告/错误-这是否暗示它合法地是特定于编译器版本的缺陷?
// main.c #includetypedef 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; }
如果上述我的理解是正确的,a void**
仍然只是一个指针,这应该是安全的。
有没有不使用左值来解决此编译器特定警告/错误的解决方法?也就是说,我理解这以及为什么这可以解决问题,但是我想避免这种方法,因为我想利用freeFunc()
NULL来实现预期的out-arg:
void* tmp = f; freeFunc( &tmp ); f = NULL;
问题编译器(之一):
user@8d63f499ed92:/build$ /usr/local/crosstool/x86-fc3/bin/i686-fc3-linux-gnu-gcc --version && /usr/local/crosstool/x86-fc3/bin/i686-fc3-linux-gnu-gcc -Wall -O2 -Werror ./main.c i686-fc3-linux-gnu-gcc (GCC) 3.4.5 Copyright (C) 2004 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ./main.c: In function `main': ./main.c:21: warning: dereferencing type-punned pointer will break strict-aliasing rules user@8d63f499ed92:/build$
不兼容的编译器(众多)之一:
user@8d63f499ed92:/build$ /usr/local/crosstool/x86-rh73/bin/i686-rh73-linux-gnu-gcc --version && /usr/local/crosstool/x86-rh73/bin/i686-rh73-linux-gnu-gcc -Wall -O2 -Werror ./main.c i686-rh73-linux-gnu-gcc (GCC) 3.2.3 Copyright (C) 2002 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. user@8d63f499ed92:/build$
更新:我进一步发现警告似乎是专门在使用编译时生成的-O2
(仍然仅使用注明的“问题编译器”)
type的值void**
是指向type的对象的指针void*
。类型Foo*
的对象不是类型的对象void*
。
类型和的值之间存在隐式转换。此转换可能会更改值的表示形式。同样,您可以编写并且具有定义为value 的明确定义的行为,但是具有未定义的行为(实际上,在任何通用体系结构上都不会将其设置为“ pointer to ”)。Foo*
void*
int n = 3; double 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
是指向释放对象的单个指针。
void *
C标准特别对待A ,部分原因是A引用了不完整的类型。这种处理也未延伸到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; })
取消引用类型查询指针是UB,您不能指望会发生什么。
不同的编译器会生成不同的警告,因此,可以将同一编译器的不同版本视为不同的编译器。对于您所看到的差异,这似乎比对体系结构的依赖性更好。
一个可以帮助您理解为什么类型修剪在这种情况下可能会很糟糕的情况是,您的函数不能在的架构上运行sizeof(Foo*) != sizeof(void*)
。尽管我不知道这是真的,但是这是标准授权的。
一种解决方法是使用宏而不是函数。
请注意,它free
接受空指针。