使用 (void *) 作为通用数据容器时的 L 值问题

Posted

技术标签:

【中文标题】使用 (void *) 作为通用数据容器时的 L 值问题【英文标题】:L-value trouble when using a (void *) as a generic data container 【发布时间】:2012-06-20 15:06:36 【问题描述】:

这是一个程序中使用的结构:

struct basic_block

  void * aux;

  /* Many other fields,
  which are irrelevant.  */
;

现在:

    在程序执行期间存在多个basic_block 实例。 程序分阶段/通过,一个接一个地执行。 aux 字段用于在一个阶段执行期间存储阶段和basic_block 特定数据,并由阶段本身释放(以便下一阶段可以重用它)。这就是为什么它是void *

我的舞台使用aux来存储一个struct,所以每次我想访问一些东西时,我都要做一个演员:

( (struct a_long_struct_name *) (bb->aux))->foo_field = foo;

现在,我的问题是:每次都像这样转换它很痛苦,而且当它是更复杂表达式的一部分时,很难阅读。我提出的解决方案是:使用宏为我做演员:

#define MY_DATA(bb) \
  ( (struct a_long_struct_name *) (bb)->aux)

然后我可以通过以下方式访问我的数据:

MY_DATA(bb)->foo_field = foo;

但是:我不能使用MY_DATA(bb) 自身 作为 L 值(对于 malloc),所以我使用该宏毕竟不是一个好主意:

/* WRONG! I cannot assign to the result of a cast:  */
MY_DATA(bb) = malloc (sizeof (struct a_long_struct_name));

我的问题:

我该怎么做才能以干净的方式引用 aux 并且仍然能够将其用作 L 值。

【问题讨论】:

[我已删除我的答案]。在那种情况下,我不明白你想做什么。你有一个 void * 指针,你想把它转换成一个不同的指针,然后给它分配一个 void *(来自 malloc)? 在问题中我“将其转换为不同的指针,然后将 void *(来自 malloc)分配给它”这一事实确实是一个副作用,并且不是真正的问题。我知道强制转换的结果绝不是 L 值。 @OliCharlesworth - 我已经编辑了我的问题,希望它更清楚。 你不需要任何演员,只要bb->aux = malloc(... @VladLazarenko - 宏的目的是隐藏我正在使用bb->aux 的事实,并在我的整个过程中使用统一的表达式在任何地方引用它阶段。 【参考方案1】:

您可以将该字段的地址转换为struct a_long_struct_name **,然后取消引用它:

#define MY_DATA(bb) \
   (* ((struct a_long_struct_name **) &(bb)->aux) )

这将适用于您展示的两种构造。

如果aux 的具体类型的可能性集合可以在struct basic_block 被声明的地方知道,那么使用联合会更简洁:

struct basic_block

  union 
      struct a_long_struct_name *alsn;
      /* etc */
   aux;

  /* Many other fields,
     which are irrelevant.  */
;

然后

#define MY_DATA(bb) ((bb)->aux.alsn)

【讨论】:

+1。第一个解决方案很有意义,我打算使用它。虽然第二个更好,但我个人不能使用它,因为 aux 已被其他阶段按原样使用。 我标记这个是因为这里提供的第一个解决方案是我最终使用的解决方案。来自未来的访客:还要看看 Jim Balter 的回答。【参考方案2】:

试试:

#define MY_DATA(bb) \
  ( *(struct a_long_struct_name **) &(bb)->aux)

请注意,这会引入别名,因此您必须在使用宏时保持一致。

【讨论】:

【参考方案3】:

没有必要用宏调用乱扔代码:

struct a_long_struct_name* strp = malloc(sizeof *strp);
if (!strp)
    <handle OOM>
bb->aux = strp;
strp->foo_field = foo;
etc.

【讨论】:

您的替代方案很好,但是当它说 Zack 的答案很好时,我犹豫要 +1。它可能在大多数情况下偶然起作用,但它不是有效的 C 语言,编译器可以并且确实会进行优化以破坏它。【参考方案4】:

你不能。 C 禁止通过不同类型的左值表达式 (struct a_long_struct_name *) 访问一种类型的对象 (void *),违反此规则是违反别名的行为。从实际的角度来看,这意味着编译器可以假设这两个左值不能引用相同的内存,因此可能会以严重破坏代码的方式重新排序读取和写入。形式上,这只是未定义的行为,这意味着任何事情都可能发生。

您应该做的是在问题中使用您的代码的原始版本,而忘记使用 MY_DATA(bb) 作为左值。而是直接分配给bb-&gt;aux。这就是void * 的用途。

【讨论】:

+1/ 感谢您发布此答案,特别是指出关于两个值不会相互混淆的一点。我明天再看看这个。现在,我面前没有代码,我不想在没有加倍确定的情况下不接受 [我会尝试造成损坏]

以上是关于使用 (void *) 作为通用数据容器时的 L 值问题的主要内容,如果未能解决你的问题,请参考以下文章

Typescript 中与通用容器混淆的错误

C++(笔记)容器(vector)作为函数参数如何传参

Generic Programming v1

STL 容器

使用 AKS 管理容器时的问题

在 C 中为链表编写通用搜索函数?