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

2019-09-12 03:12发布

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.

4条回答
一夜七次
2楼-- · 2019-09-12 03:14

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.
查看更多
forever°为你锁心
3楼-- · 2019-09-12 03:18

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.

查看更多
Explosion°爆炸
4楼-- · 2019-09-12 03:31

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.

查看更多
Fickle 薄情
5楼-- · 2019-09-12 03:34

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)
查看更多
登录 后发表回答