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


38

我已经阅读 有关Stack Overflow RE的各种文章 :取消引用类型标记的指针错误。我的理解是,该错误本质上是编译器警告,它警告通过不同类型的指针访问对象的危险(尽管似乎为产生了例外),这是可以理解且合理的警告。 char*

我的问题特定于以下代码:为什么将指针的地址强制转换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;
}

如果上述我的理解是正确的,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(仍然仅使用注明的“问题编译器”)



1
“一个 void**,仍然只是一个指针,这应该是安全的。” 哇!听起来您在进行一些基本假设。试着想象的那么字节和杠杆方面,更在抽象的方面,因为这是你实际上与编程
亮度种族在轨道

7
切线地,您使用的编译器是1517年!我不会依靠任何一个。
塔维安·巴恩斯

4
@TavianBarnes此外,如果您出于任何原因必须依赖GCC 3,我认为最好使用报废的最终版本,即3.4.6。为什么在搁置该系列之前不利用该系列的所有可用修复程序。
哈兹(Kaz)

哪种C ++编码标准规定了所有这些空格?
彼得·莫滕森

Answers:


33

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

之间存在隐式转换 类型Foo*和的void*。此转换可能会更改值的表示形式。同样,您可以编写,int n = 3; double x = n;并且具有定义x为value 的明确定义的行为3.0,但是double *p = (double*)&n;具有未定义的行为(实际上,不会将其设置p为“ pointer to3.0在任何通用体系结构上都不 ”)。

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

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

编译器可以自由地告诉您您做错了什么,或者悄悄地执行了您不需要的操作,或者悄悄地执行了您想要的操作。未定义的行为允许任何这些行为。

您可以创建freefunc一个宏:

#define FREE_SINGLE_REFERENCE(p) (free(p), (p) = NULL)

这带有宏的通常限制:缺乏类型安全性,p被评估两次。请注意,如果仅p是指向释放对象的单个指针,则这样做仅可确保您不留下悬挂的指针。


1
它是很好的知道,即使Foo*void*对你的架构相同的表示,它仍然不确定至类型双关语他们。
塔维安·巴恩斯

12

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; })

3
+1,用于更好地实现预期的行为。我看到的唯一问题#define是它将评估obj两次。不过,我不知道避免这种第二次评估的好方法。甚至语句表达式(GNU扩展名)也无法解决问题,因为obj在使用它的值后需要将其分配给它。
cmaster-恢复莫妮卡

2
@cmaster:如果你愿意使用GNU扩展,如语句表达式,那么你可以使用typeof,以避免评估obj两次#define freeFunc(obj) ({ typeof(&(obj)) ptr = &(obj); free(*ptr); *ptr = NULL; })
ruakh

@ruakh非常酷:-)如果dbush可以将其编辑为答案,那就太好了,因此它不会随注释一起被大量删除。
cmaster-恢复莫妮卡

9

取消引用类型化指针是UB,您不能指望会发生什么。

不同的编译器会生成不同的警告,因此,可以将同一编译器的不同版本视为不同的编译器。对于您所看到的差异,这似乎比对体系结构的依赖性更好。

一个可以帮助您理解为什么在这种情况下类型修剪可能会很糟糕的情况是您的功能无法在以下架构上使用: sizeof(Foo*) != sizeof(void*)。尽管我不知道这是真的,但是这是标准授权的。

一种解决方法是使用宏而不是函数。

请注意,它free接受空指针。


2
令人着迷的是sizeof Foo* != sizeof void*。我从未遇到过“狂野”的指针大小依赖于类型的情况,因此多年来,我开始认为指针大小在给定的体系结构上都是相同的,这是理所当然的。
StoneThrow

1
@Stonethrow的标准示例是用于在字可寻址体系结构中寻址字节的胖指针。但是我认为当前的单词可寻址机器使用替代的sizeof char == sizeof word
AProgrammer

2
请注意,该类型已被括号用于sizeof ...
安蒂·哈帕拉

@StoneThrow:无论指针大小如何,基于类型的别名分析都会使其不安全;这可以帮助编译器通过假设存储区float*不会修改int32_t对象来进行优化,例如,编译器int32_t*无需int32_t *restrict ptr假设它没有指向相同的内存。对于void**假定不修改Foo*对象的商店也是如此。
彼得·科德斯

4

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

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

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

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

  • 与对象的有效类型兼容的类型的限定版本,

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

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

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

  • 字符类型。

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

在6.7.5.1第2段中,

为了使两个指针类型兼容,两个指针必须具有相同的资格,并且都应是指向兼容类型的指针。

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

尽管不是代码无效的技术原因,但也应注意第6.2.5节第26段:

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

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


2

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

  1. 自由删除功能,就像您发现警告的功能一样。
  2. 分配函数避免了返回的标准C语言习惯void *(此问题不会发生此问题,因为它涉及到值转换而不是类型punning),而是返回错误标志并通过指针到指针存储结果。

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

对于(2),两者cudaMallocposix_memalign均为示例。

在两种情况下,接口都不固有地要求无效使用,但强烈鼓励使用该接口,并且仅允许使用额外的临时对象(其类型void *破坏了自由-掏空功能的目的)并使分配笨拙,才允许正确使用。


您是否有一个链接解释更多有关为什么(1)是反模式的信息?我认为我不熟悉这种情况/论据,并且想了解更多信息。
StoneThrow

1
@StoneThrow:非常简单-目的是通过使存储指向要释放的内存的指针的对象为零来防止滥用,但实际上唯一的方法是调用者实际上将指针存储在对象中void *每次要取消引用它时,键入并转换/转换它。这是极不可能的。如果调用者存储了其他类型的指针,则不调用UB调用函数的唯一方法是将指针复制到类型为temp的对象,void *并将其地址传递给释放函数,然后将其传递给...
R .. GitHub停止帮助ICE

1
...将临时对象而不是调用者拥有指针的实际存储空间为空。当然,实际发生的情况是该函数的用户最终进行了(void **)强制转换,从而产生了未定义的行为。
R .. GitHub停止帮助ICE,

2

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.