7

I was reading somewhere about how some object oriented features can be implemented in C, and it has proven fairly useful. In specific, I have been toying with the idea of inheritance. Here is an example:

typedef struct Circle{
     int rad, x, y;
     //Other things...
} Circle;

typedef struct Entity{
    Circle body;
    //Entity-specific items...
} Entity;

This is simple, but it allows for something sneaky. A pointer to an Entity is ALSO a pointer to a Circle because the first element of an Entity is always a Circle. With this idea, we can construct the following function:

int checkCircleCollision(Circle* one, Circle* two);

And call it as such:

Entity* myentity = createEntity(/* Things specific to my entity */);
Entity* myotherentity = createEntity(/* Different things */);
//Did they collide?
if (checkCircleCollision(myentity, myotherentity)){
    /* ... */
}

This is wonderful, but I ran into a problem. What if I wanted some of my Entities to be rectangles, too? I have a solution, but I'd like confirmation that it will always work, no matter the compiler. My knowledge on unions is very limited.

//Circle defined as above...
typedef struct Rectangle{
    int x, y, w, h;
    //Other things...
} Rectangle;

int checkRectangleCollision(Rectangle* one, Rectangle* two);
int checkRectangleCircleCollision(Rectangle* rect, Circle* circ);

typedef struct Entity{
     union{
         Rectangle rect;
         Circle circ;
     } shape;
     int type;
     //Entity things...
}

Is it now completely safe to assume that the first element of an Entity is either a Rectangle or a Circle, depending on its initialization? Furthermore, could it be used in any of the three functions described above? Bonus points for relevant quotes from the standard. To be perfectly clear, I'd like to do this:

Entity* rectentity = createEntity(RECTANGLE, /* width/height/etc */);
Entity* circentity = createEntity(CIRCLE, /* rad/x/y/etc */ );
if (checkRectangleCircleCollision(rectentity, circentity)){
     /* ... */
}
Yu Hao
  • 119,891
  • 44
  • 235
  • 294
BrainSteel
  • 669
  • 5
  • 17
  • "type punning" might be a key word you want to search for. – mafso Dec 29 '13 at 00:42
  • It would be easier to debug if your type was at the start of the structure instead of at the bottom. Whenever you add a new structure to the union, the position of type shifts. If you are using a debugger without source code, having the type at the top helps. – cup Dec 29 '13 at 00:45
  • 1
    @BrainSteel Is Entity the parent class of circle and rectangle? – this Dec 29 '13 at 00:46
  • @self. Yes. @cup It can't be at the top because then a pointer to an `Entity` is also a pointer to an `int` named `type`, and not a pointer to a `union`. – BrainSteel Dec 29 '13 at 02:40
  • 3
    @BrainSteel Instead of embedding child struct in the Entity struct, do the opposite. Declare an Entity struct only with it's own members. Then when you want to add a Circle, make a Circle struct and declare an Entity struct inside Circle struct at the beggining of it. That way you don't have to deal with type. Every child has it's parent at the beginning of the struct, instead of every parent having multiple possible children inside an union. – this Dec 29 '13 at 03:23
  • @self. +1. Moreover, that approach is flexible (you don't have to modify parent class to add child class), and memory efficient (`Circle` takes space of 4 ints, although it uses only 3). – el.pescado - нет войне Dec 29 '13 at 08:10
  • @self. Correct me if I'm wrong, but does that not imply that all `Circle`s must be `Entity`s? Indeed, I have a second parent class which may need to be a `Circle`. – BrainSteel Dec 31 '13 at 10:09
  • Yes, every Circle is a child of Entity. If you want a class to inherit from Circle it has to be its child not parent. – this Dec 31 '13 at 11:28

2 Answers2

4

Is it now completely safe to assume that the first element of an Entity is either a Rectangle or a Circle, depending on its initialization?

Yes.

Bonus points for relevant quotes from the standard.

  • "A union type describes an overlapping nonempty set of member objects, each of which has an optionally specified name and possibly distinct type." (C99, 6.2.5.20)

  • "A pointer to a union object, suitably converted, points to each of its members (or if a member is a bit- field, then to the unit in which it resides), and vice versa." (6.7.2.1.14)

  • @FiddlingBits Awww, then I don't get any bonuses today... I must admit, I have the standard PDF in my Documents folder! –  Dec 29 '13 at 00:48
  • Thank you! This is precisely what I was looking for. Where did you get this PDF? Also, I'm not that picky about bonus points. Memorized would've been quite impressive though. I would've had to buy you a cookie. – BrainSteel Dec 29 '13 at 02:38
  • @BrainSteel You're welcome. [Here's the ultimate source of standards](http://stackoverflow.com/questions/81656/where-do-i-find-the-current-c-or-c-standard-documents). –  Dec 29 '13 at 09:14
  • A pointer to an `Entity` is a pointer to a struct, not to a union. There is a different paragraph in the standard for this. And, while a suitably converted pointer to a struct points to its first member, and, if that member is a union, the new pointer, when converted again to point to a type of a member, points to that member, it is not clear that a single conversion directly from a pointer to the struct to a pointer to a member of the struct’s first member is fully defined by the C standard. – Eric Postpischil Dec 29 '13 at 09:41
  • 1
    @EricPostpischil "A pointer to an Entity is a pointer to a struct, not to a union. There is a different paragraph in the standard for this." - Yes, and if you read the question, you will perceive that OP is already aware of that "a pointer to an `Entity` is also a pointer to a `Circle` because the first element of an `Entity` is always a `Circle`". Furthermore, I didn't make any assertions about the number of conversions needed. I only answered the question. If you are right, then two casts are necessary to make it work, and that's OK, I didn't say it is not the case. –  Dec 29 '13 at 09:49
  • @H2CO3: The OP’s statement is incorrect; a pointer to an `Entity` is not a pointer to a `Circle`. It must be converted. The code in the question shows how the conversion will be done: By passing a pointer to an `Entity` as an argument for a parameter of a pointer to another type, an implicit conversion is induced. So a single conversion is used. – Eric Postpischil Dec 29 '13 at 09:58
  • @EricPostpischil Indeed, OP's statement is incorrect; I should have written that he is aware of the fact that "A pointer to a structure object, suitably converted, points to its initial member [...], and vice versa" (6.7.2.1.13). However: "The code in the question shows how the conversion will be done" - but is that appropriate? As I understand, an explicit conversion is necessary, and I was intending to show that by including the second quote. So the code in the question which is supposed to demonstrate the use case is faulty, isn't it? –  Dec 29 '13 at 10:05
  • @EricPostpischil (You're right that probably I should have addressed this more explicitly; I didn't because I always use explicit conversion in cases like this, and I didn't even consider that my answer would distract OP from doing so...) –  Dec 29 '13 at 10:10
  • @H2CO3: Yes, I believe the code showing the use is faulty. The standard says that arguments are converted to parameter types as if by assignment, and the assignment clause says the left and right sides of the assignment must be pointers to compatible types (or other acceptable cases) (C 2011 [N1570] 65.16.1 1). However, Apple gcc 4.2.1 accepts this with only a warning, even with `-std=c99 -pedantic`. For proper code (by the standard) and no warnings (by gcc), explicit casts should be used. – Eric Postpischil Dec 29 '13 at 12:18
  • @EricPostpischil Great, then we agree. –  Dec 29 '13 at 12:21
  • @H2CO3 At the time of posting, I had forgotten about the necessity of explicit casting. My compiler issued a warning the first time I attempted a build, and the cast has been explicit since. My original code is indeed faulty. My question should more accurately portray my inquiry over whether or not an explicit cast is legal in all cases. – BrainSteel Dec 31 '13 at 04:57
  • @BrainSteel No problem, if you know that, then it's fine. Just do the casting and it will be OK. –  Dec 31 '13 at 08:15
4

A union, regardless of its active field, is always aligned on the same memory address. For example, consider the following:

#include <stdio.h>

int main(void)
{  
    union typeUnion
    {
        int i;
        float f; 
    } u;

    u.i = 5;    
    printf("%-4d (%p)\n", u.i, &u.i);

    u.f = 3.14;
    printf("%.2f (%p)", u.f, &u.f);

    return 0;
}

Output on my machine:

5    (0x22aac0) 
3.14 (0x22aac0)
Fiddling Bits
  • 8,712
  • 3
  • 28
  • 46