联合与无效指针

时间:2009-11-30 18:23:02

标签: c unions

简单地使用void *与union之间的区别是什么?例如:

struct my_struct {
    short datatype;
    void *data;
}

struct my_struct {
    short datatype;
    union {
        char* c;
        int* i;
        long* l;
    };
};

这两个都可以用来完成同样的事情,使用union或void *会更好吗?

11 个答案:

答案 0 :(得分:16)

我的库中确实存在这种情况。我们有一个通用的字符串映射模块,可以使用不同的索引大小,8位,16位或32位(出于历史原因)。所以代码中充满了代码:

if(map->idxSiz == 1) 
   return ((BYTE *)map->idx)[Pos] = ...whatever
else
   if(map->idxSiz == 2) 
     return ((WORD *)map->idx)[Pos] = ...whatever
   else
     return ((LONG *)map->idx)[Pos] = ...whatever

有100条这样的线。在第一步中,我将其更改为联合,我发现它更具可读性。

switch(map->idxSiz) {
  case 1: return map->idx.u8[Pos] = ...whatever
  case 2: return map->idx.u16[Pos] = ...whatever
  case 3: return map->idx.u32[Pos] = ...whatever
}

这使我能够更好地了解发生了什么,然后我可以决定仅使用32位索引完全删除idxSiz变体。但只有在代码更具可读性后才能实现这一点。 PS:这只是我们项目的一小部分,大约是由不再存在的人编写的几十万行代码。所以代码的变化是渐进的,以免破坏应用程序。

结论:即使人们不太习惯联合变体,我也更喜欢它,因为它可以使代码更轻松地阅读。在大型项目中,使代码更具可读性非常重要,即使您自己稍后会阅读它。

修改:添加了评论,因为评论不会格式化代码:

之前的切换更改(现在是真正的代码)

switch(this->IdxSiz) { 
  case 2: ((uint16_t*)this->iSort)[Pos-1] = (uint16_t)this->header.nUz; break; 
  case 4: ((uint32_t*)this->iSort)[Pos-1] = this->header.nUz; break; 
}

已更改为

switch(this->IdxSiz) { 
  case 2: this->iSort.u16[Pos-1] = this->header.nUz; break; 
  case 4: this->iSort.u32[Pos-1] = this->header.nUz; break; 
}

我不应该将我在代码中所做的所有美化结合在一起,只显示该步骤。但我在家里发布了我无法访问代码的答案

答案 1 :(得分:11)

在我看来,void指针和显式转换是更好的方法,因为对于每个经验丰富的C程序员来说,意图是显而易见的。

编辑澄清:如果我在程序中看到所述联合,我会问自己作者是否想要限制存储数据的类型。也许执行一些健全性检查,这些检查仅对整数类型有意义。 但是如果我看到一个void指针,我直接知道作者设计的数据结构可以保存任意数据。因此,我也可以将它用于新引入的结构类型。 请注意,我可能无法更改原始代码,例如如果它是第三方库的一部分。

答案 2 :(得分:6)

使用union来保存实际对象而不是指针更常见。

我认为我尊重的大多数C开发人员都懒得将不同的指针结合在一起;如果需要通用指针,只需使用void *肯定是“C路”。这种语言牺牲了很多安全性,以便你有意识地对事物的类型进行别名;考虑到我们为此功能付出的代价,我们不妨在简化代码时使用它。这就是为什么逃避严格打字一直存在的原因。

答案 3 :(得分:5)

union方法要求您知道先验可能使用的所有类型。 void *方法允许存储在编写相关代码时可能甚至不存在的数据类型(尽管使用这样的未知数据类型可能很棘手,例如需要传递指针到一个要在该数据上调用的函数,而不是直接处理它。)

编辑:由于似乎对如何使用未知数据类型存在一些误解:在大多数情况下,您提供某种“注册”功能。在典型的情况下,您将指针传递给可以执行所存储项目所需的所有操作的函数。它生成并返回一个新索引,用于标识类型的值。然后,当您想要存储该类型的对象时,将其标识符设置为从注册中返回的值,并且当使用对象的代码需要对该对象执行某些操作时,它将通过以下方式调用相应的函数。你传入的指针。在一个典型的例子中,那些指向函数的指针将在struct中,并且它只是存储(指向)数组中的那些结构。它从注册返回的标识符值只是它存储了这个特定结构的那些结构数组的索引。

答案 4 :(得分:2)

虽然现在使用union并不常见,但由于union对于你的使用场景来说更加明确,所以很适合。在第一个代码示例中,它不了解数据的内容。

答案 5 :(得分:2)

我的偏好是去工会路线。来自void *的演员阵容是一种钝器,通过正确键入的指针访问基准点会带来一些额外的安全性。

答案 6 :(得分:2)

抛硬币。 Union更常用于非指针类型,因此在这里看起来有点奇怪。但是它提供的显式类型规范是不错的隐式文档。 void *会很好,只要你总是知道你只会访问指针。不要开始在那里放置整数并依赖sizeof(void *)== sizeof(int)。

我觉得这两种方式最终都没有任何优势。

答案 7 :(得分:2)

在你的例子中有点模糊,因为你使用的是指针,因此是间接的。但union肯定有其优点。

想象:

struct my_struct {
   short datatype;
   union {
       char c;
       int i;
       long l;
   };
};

现在您不必担心值部分的分配来自何处。没有单独的malloc()或类似的东西。您可能会发现对->c->i->l的访问速度要快一些。 (尽管如果有很多这样的访问,这可能只会产生影响。)

答案 8 :(得分:2)

如果您在其他编译器上使用-fstrict-aliasing(gcc)或类似选项构建代码,那么您必须非常小心如何进行转换。您可以根据需要强制转换指针,但是当您取消引用它时,用于取消引用的指针类型必须与原始类型匹配(有一些例外)。例如,您不能执行以下操作:

void foo(void * p)
{
   short * pSubSetOfInt = (short *)p ;
   *pSubSetOfInt = 0xFFFF ;
}

void goo()
{
   int intValue = 0 ;

   foo( &intValue ) ;

   printf( "0x%X\n", intValue ) ;
}

如果在构建优化时打印0(比方说)而不是0xFFFF或0xFFFF0000,请不要惊讶。使代码工作的一种方法是使用union做同样的事情,代码也可能更容易理解。

答案 9 :(得分:1)

这实际上取决于你想要解决的问题。如果没有这种背景,那么评估哪种情况会更好是不可能的。

例如,如果您正在尝试构建一个通用容器(如列表或可以处理任意数据类型的队列),那么最好使用void指针方法。 OTOH,如果你将自己局限于一小部分原始数据类型,那么联合方法可以节省你一些时间和精力。

答案 10 :(得分:1)

联合为最大的成员保留足够的空间,它们不必相同,因为void *具有固定的大小,而union可以用于任意大小。

#include <stdio.h>
#include <stdlib.h>

struct m1 {
   union {
    char c[100];
   };
};

struct m2 {
    void * c;
 };


 int
 main()
 {
printf("sizeof m1 is %d ",sizeof(struct m1));
printf("sizeof m2 is %d",sizeof(struct m2));
exit(EXIT_SUCCESS);
 }

输出: sizeof m1是100 sizeof m2是4

编辑:假设您只使用与void *相同大小的指针,我认为联合更好,因为当您尝试使用整数设置.c时,您将获得一些错误检测指针等。 void *,除非你创建自己的分配器,否则肯定是快速和肮脏的,无论好坏。