L-value trouble when using a (void *) as a generic data container

梦想与她 提交于 2019-12-13 03:38:57

问题


Here is a structure used in a program:

struct basic_block
{
  void * aux;

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

Now:

  1. Several instances of basic_block exist during the execution of the program.
  2. The program works in stages/passes, which are executed one after another.
  3. The aux field is meant for storing stage and basic_block specific data during the execution of a stage, and freed by the stage itself (so the next stage can reuse it). This is why it is a void *.

My stage uses aux to store a struct, so each time I want to access something, I have to do a cast:

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

Now, my problem: Casting it each time like this is a pain, and difficult to read when it is part of more complicated expressions. My proposed solution was: Use a macro to do the cast for me:

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

Then I can access my data with:

MY_DATA(bb)->foo_field = foo;

But: I cannot use MY_DATA(bb) itself as an L-value (for a malloc), so I using that macro isn't such a good idea after all:

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

My question:

What can I do in order to refer to aux in a clean way and still be able to use it as an L-value.


回答1:


You can cast the address of the field to struct a_long_struct_name ** and then dereference it:

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

This will work in both the constructs you have shown.

If the set of possibilities for the concrete type of aux can be known at the point where struct basic_block is declared, though, it would be cleaner to use a union:

struct basic_block
{
  union {
      struct a_long_struct_name *alsn;
      /* etc */
  } aux;

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

and then

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



回答2:


Try:

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

Note that this introduces aliasing so you'll have to be consistent in your use of the macro.




回答3:


It isn't necessary to litter your code with macro calls:

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



回答4:


You can't. C forbids accessing an object of one type (void *) via an lvalue expression of different type (struct a_long_struct_name *), and breaking this rule is an aliasing violation. What that means from a practical standpoint is that the compiler is allowed to assume these two lvalues cannot refer to the same memory, and thus may reorder reads and writes in a way that breaks your code badly. Formally, it's just undefined behavior, which means anything could happen.

What you should do is use the original version of your code in the question, and forget about using MY_DATA(bb) as an lvalue. Instead just assign directly to bb->aux. This is what void * is for.



来源:https://stackoverflow.com/questions/11122491/l-value-trouble-when-using-a-void-as-a-generic-data-container

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!