联合与无效指针

使用简单的void *而不是union的区别是什么? 例:

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

这两个都可以用来完成完全相同的事情,是否更好地使用union或void *虽然?

我的库中确实存在这种情况。 我们有一个通用的字符串映射模块,可以使用不同的索引大小,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; } 

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

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

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

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

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

union方法要求您事先知道可能使用的所有类型。 void *方法允许存储在编写相关代码时可能甚至不存在的数据类型(尽管使用这种未知数据类型做很多事情可能很棘手,例如要求将指针传递给要在该数据上调用的函数能够直接处理它)。

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

虽然现在使用联合并不常见,但由于联合对于您的使用场景更为明确,因此非常适合。 在第一个代码示例中,它不了解数据的内容。

我倾向于去工会路线。 来自void *的演员是一种钝器,通过正确键入的指针访问数据可以提供一些额外的安全性。

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

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

在你的例子中有点模糊,因为你使用的是指针,因此是间接的。 但union当然确实有其优势。

想像:

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

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

如果您使用-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做同样的事情,代码也可能更容易理解。

这真的取决于你试图解决的问题。 如果没有这种背景,那么评估哪种更好是不可能的。

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

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

 #include  #include  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 *,除非你创建自己的分配器,否则肯定是快速和肮脏的,无论好坏。