0

I have a structure as below

struct things
{
  BOOL_T  is_copy;  /* is false */
  handle1 h1;
  handle2 h2;
  int     n;
  void *  memory;
};

Sometimes I make a copy of objects of things in below structure

struct copy_of_things
{
  BOOL_T  is_copy; /* is true */
  handle1 h1; /* I don't need h2 and n here */
  void *  memory;  /* copied from things object and
                      things object allocates new memory */
  int     another_member;
};

Also I have an array of structure in a manager that keeps all the things and copy_of_things structure living in my program(call it struct things *things_array[SIZE_OF_ARRAY];). I can not manage 2 arrays because of design requirements(Arrays are similar to hash). To enable this, I made the type of this array as thing * and changed the type of copy_of_things as below

struct copy_of_things
{
  struct things ths;
  int    another_member;
};

Now I can read is_copy member of my array elements and decide whether to interpret it as things or copy_of_things.

I feel this is not only inefficient in terms of memory but ugly looking.

Solution 2 I also plan to use type of array is struct of type(is_copy) and a union.

struct things {
  BOOL_T  is_copy;
  union {
    struct {  /* is_copy = false */
      handle1 h1;
      handle2 h2;
      int     n;
      void *  memory;
    } t;
    struct {  /* is_copy = true */
      handle1 h1;
      void *  memory;
      int     another_member;
    } c;
};

But while reviewing I found this design also ugly.

Solution 3 I plan to keep BOOL_T is_copy; as first member of both structure and keep array of type BOOL_T. After reading the content of BOOL_T I can de-reference my pointer to things or copy_of_things. I am not sure if this is a good solution and provides a well defined behaviour (at higher level of optimization) as same address is interpreted as different types.

Question Is there a better solution for my problem that is portable on any platform.

EDIT
Thank you for the answers. So there are two suggested alternatives.

  1. Use Unions: Downside of the approach is, it requires more memory for copies. In my case the sizeof copy_of_things is significantly smaller than sizeof things. One workaround would be alloc just enough bytes in which actual object can reside.
  2. Use a common struct and make it first member of both copy_of_things and things. Here I would end up de-referencing same memory location with 2 types (struct common and struct things or struct copy_of_things). I am not sure that strict aliasing rule won't bite me.
  3. One more solution can be keep first member of both structs as char is_copy; /* \0 if not a copy, non zero otherwise and access the pointer only as char * or things * or copy_of_things *.

Still open question
I have seen solution 2 used at many places. Is it strict aliasing rule safe? Is there a better solution to my problems as the code would be compiled on a variety of compilers. Size of reverse mapping array is large so I am avoiding to use a union or a solution that increases the size of reverse mapping. Number of things (and copy) are less, so it is okay to add new data member there.

Community
  • 1
  • 1
Mohit Jain
  • 30,259
  • 8
  • 73
  • 100
  • could you just make both structs the same type? And with the semantics that in "copy", `h2` is unused and `n` is repurposed? – M.M Jul 25 '14 at 08:51
  • @MattMcNabb Thank you. Actual structures contains about 12 members, and types are such that not all can be reused. But yes, I got your idea and will check if this solves my problem. – Mohit Jain Jul 25 '14 at 09:04

5 Answers5

4

You can share some members of these struct's with a more compact union:

struct things {
  BOOL_T  is_copy;
  handle1 h1;
  void *  memory;
  union {
    struct {  /* is_copy = false */
      handle2 h2;
      int     n;
    } t;
    struct {  /* is_copy = true */
      int     another_member;
    } c;
};
perreal
  • 94,503
  • 21
  • 155
  • 181
  • Thank you for your answer. If there are many members in `things` as compared to `copy_of_things`, this would be space inefficient. – Mohit Jain Jul 25 '14 at 13:20
1

Having a structure inside other structures is a common way of emulating inheritance in C. The important thing here is that the common structure should contain the minimal set of data common to all structures in the "inheritance" hierarchy, and that it must always be the first member in the inheriting structures.

The first important thing, about containing the common members, is because if there's no common data then the structures are totally unrelated and there's no need to associate them with each other.

The other important thing, about putting the structure first, is because then the offsets of the common members will be the same, and you can easily treat a pointer to the bigger structure as a pointer to the smaller base structure.


For example, in your two first structure, the is_copy member will have different offset depending on which structure you have a pointer to, so you need to know which structure the pointer is pointing to before you can access the is_copy member, which kind of defeats your purpose.

The other way with you placing the base structure as a member inside the extended structure is what I'm talking about above.


However, if you're just using those two structures, and never will extend to more, then using the version with unions might probably be the best way of handling it.


As for portability, as long as you don't transfer the structures between platforms or using different compiler for different parts of your application, then the union version is most source-code portable. The "inheritance" scheme will work on all modern PC-like systems and their compilers, and have done so for a long time, but there's no guarantee that it will work on all systems and all compilers (if you're planning on porting the code to some rare system with weird hardware and compiler you might want to look out, but the cases where the "inheritance" scheme will not work is very small, and most people will never come in contact with such a system in their entire lifetime.)

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • Thank you for answering. So union looks like the best decision. Although copy is seldomly used, but I understand and agree with your reasonings. – Mohit Jain Jul 25 '14 at 09:05
1

I am not familiar with many of the words here, but hey, consider the following suggestion of mine:

struct things
{
    BOOL_T is_copy;
    union
    {
        struct
        {
            handle1 h1;
            handle2 h2;
            int     n;
            void * memory;
        } * t;
        struct
        {
            handle1 h1;
            void * memory;
            int another_member;
        } * c;
    };
};

Looks awfully similar to the one in your question, although with one key difference. Notice the asterisks behind the variable names, making them a pointer to the structures.

Both t and c will have the same size this way, therefore there'll be no extraneous memory while using either one of the branches of the union. It's a bit similar to sclarke81's answer, but here it's more distinct, also you won't have to typecast the void * object to the structure you've allocated the memory for to access their members. I.e., you can just write the following:

if (mything.is_copy)
{
    mything.c->another_member;
} else {
    mything.t->n;
}

Instead of:

if (mything.obj_type == copy)
{
    ((struct copy_of_things) mything.object)->another_member;
} else {
    ((struct things) mything.object)->n;
}

I have no idea if that covers your needs, just a tip.

Community
  • 1
  • 1
Utkan Gezer
  • 3,009
  • 2
  • 16
  • 29
  • Thank you for the helping hand. This is an interesting suggestion. But this also moves the is_copy to my reverse mapping array which is very large. Keeping it per object would be beneficial for me. – Mohit Jain Aug 06 '14 at 09:15
0

How about something like this:

enum object_types
{
   thing,
   copy
};

struct thing_array_item
{
   void *object;
   enum object_types obj_type;
};

struct thing_array_item *things_array[ SIZE_OF_ARRAY ];

Doing this means that a thing_array_item has a pointer to your object and the enum tells you how to interpret it. You can just malloc and free the memory for the objects as you wish.

An alternative would be to do away with the enum:

struct thing_array_item
{
   struct things *obj1;
   struct copy_of_things *obj2;
};

With this you'd just set the pointer that you're not using to NULL. Then the non-null pointer in any thing_array_item is the pointer you use.

sclarke81
  • 1,739
  • 1
  • 18
  • 23
0

I got many good ideas for my problem. But it seems I was unable to record all the details of my question very effectively.

Here is the final solution I came up with:

[1] Write a common structure that contains the members that are accessed by through the reverse mapping.

struct common {
  handle1 h1;
  void *  memory;
};

[2] Now keep this as member in both structures.

struct things
{
  handle2 h2;
  int     n;
  struct common cmn;
};
struct copy_of_things
{
  BOOL_T  is_copy; /* is true */
  int     another_member;
  struct common cmn;
};

[3] Change the type of reverse mapping to struct common *

This adds very slight non-readability to my code, but meets all other requirements for me.

Mohit Jain
  • 30,259
  • 8
  • 73
  • 100