为什么此声明取消引用编译器特定的类型标记指针警告?

我已阅读various posts on Stack Overflow RE:取消引用类型标记的指针错误。我的理解是,该错误实质上是编译器警告,它警告通过不同类型的指针访问对象的危险(尽管char*似乎有例外),这是可以理解且合理的警告。 / p>

我的问题特定于以下代码:为什么强制将指针的地址转换为void**才能获得此警告(通过-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;
}

如果以上所述我的理解是正确的,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编译时生成的(仍然仅使用指出的“问题编译器”)

yanglu06550132 回答:为什么此声明取消引用编译器特定的类型标记指针警告?

类型void**的值是指向类型void*的对象的指针。类型为Foo*的对象不是类型为void*的对象。

类型Foo*void*之间存在隐式转换。此转换可能会更改值的表示形式。同样,您可以编写int n = 3; double x = n;,这具有将x设置为值3.0的明确定义的行为,但是double *p = (double*)&n;具有未定义的行为(实际上不会设置p指向任何常见架构上的“指向3.0的指针”。

如今,不同类型的对象指针具有不同表示形式的体系结构已很少见,但C标准允许它们。有(稀有)旧机器带有 word指针 byte指针,这些指针是一个单词在内存中的地址,而字节指针是一个单词的地址以及该单词的字节偏移量。在此类体系结构上,Foo*将是字指针,void*将是字节指针。有(稀有)带有 fat指针的机器,它们不仅包含有关对象地址的信息,而且还包含有关对象的类型,大小和访问控制列表的信息。指向确定类型的指针可能与void*具有不同的表示形式,freefunc在运行时需要其他类型信息。

这种机器很少见,但C标准允许。一些C编译器利用此权限将类型标记的指针视为与众不同的指针,以优化代码。指针混叠的风险是限制编译器优化代码能力的主要限制,因此编译器倾向于利用此类权限。

编译器可以自由地告诉您您做错了什么,或者安静地执行您不想要的操作,或者安静地执行您想要的操作。未定义的行为允许其中任何一种。

您可以将#define FREE_SINGLE_REFERENCE(p) (free(p),(p) = NULL) 设为宏:

p

这带有宏的通常限制:缺乏类型安全性,p被评估两次。请注意,如果A <- c("G","C","K") B <- c("S","B","A") C <- c("A","M","C") 是指向已释放对象的单个指针,则这样做只给您提供安全的地方,不会留下悬空的指针。

,

C标准对void *进行特殊处理,部分原因是它引用了不完整的类型。这种处理不会扩展到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接受空指针。

,

根据C标准,此代码无效,因此它在某些情况下可能有效,但不一定可移植。

在6.5第7段中找到了用于通过已强制转换为其他指针类型的指针访问值的“严格别名规则”:

  

只能通过具有以下类型之一的左值表达式访问对象的存储值:

     
      
  • 与对象的有效类型兼容的类型

  •   
  • 与对象的有效类型兼容的类型的合格版本

  •   
  • 一种类型,它是与对象的有效类型相对应的有符号或无符号类型

  •   
  • 一种类型,它是与对象的有效类型的限定版本相对应的有符号或无符号类型

  •   
  • 聚集或联合类型,其成员中包括上述类型之一(递归地包括子聚集或包含的联合的成员),或者

  •   
  • 字符类型。

  •   

在您的*obj = NULL;语句中,该对象的有效类型为Foo*,但是由类型为*obj的左值表达式void*访问。

在6.7.5.1第2段中,我们有

  

要使两个指针类型兼容,则两者必须具有相同的限定条件,并且都应是指向兼容类型的指针。

因此void*Foo*不是兼容类型或添加了限定符的兼容类型,并且当然不适合严格别名规则的其他任何选项。

尽管代码不是技术上的无效原因,但与第6.2.5节第26段有关:

  

指向void的指针应具有与指向字符类型的指针相同的表示和对齐要求。同样,指向兼容类型的合格或不合格版本的指针应具有相同的表示形式和对齐要求。所有指向结构类型的指针应具有相同的表示和对齐要求。指向联合类型的所有指针应具有相同的表示和对齐要求。指向其他类型的指针不必具有相同的表示或对齐要求。

关于警告的差异,这不是标准需要诊断消息的情况,因此,仅取决于编译器或其版本在注意潜在问题并以有用的方式指出问题方面的能力。您注意到优化设置可以有所作为。这通常是因为内部产生了有关程序的各个部分实际上如何组合在一起的更多信息,因此,额外的信息也可用于警告检查。

,

在其他答案之前,这是C语言中的经典反模式,应该用火焚烧。它出现在:

  1. 自由删除功能,例如您发现警告的功能。
  2. 分配函数避免了返回void *的标准C习惯用法(此问题不会受到影响,因为它涉及到值转换而不是 type punning >),而不是返回错误标志并通过指针到指针存储结果。

对于(1)的另一个示例,ffmpeg / libavcodec的av_free函数中存在一个长期臭名昭著的案例。我相信它最终是通过宏或其他技巧解决的,但我不确定。

对于(2),cudaMallocposix_memalign都是示例。

在两种情况下,接口本质上都不要求无效使用,但强烈鼓励使用无效接口,并且仅允许使用void *类型的额外临时对象(不符合使用目的)的正确使用。自由放空功能,使分配变得笨拙。

,

尽管C是为在所有指针上使用相同表示形式的机器设计的,但是标准的作者希望使该语言在使用针对不同类型对象的指针使用不同表示形式的机器上可用。因此,即使许多机器可以以零成本进行操作,他们也不要求对不同类型的指针使用不同的指针表示形式的计算机支持“任何类型的指针的指针”类型。

在编写本标准之前,对于所有指针类型都使用相同表示形式的平台,其实现将一致地允许至少使用适当的转换将void**用作“指向任何指针的指针”。该标准的作者几乎可以肯定地认识到,这在支持它的平台上将是有用的,但是由于不能得到普遍的支持,他们拒绝强制执行它。相反,他们希望质量实现会在合理的情况下处理基本原理描述为“流行扩展”的构造。

本文链接:https://www.f2er.com/3121035.html

大家都在问