Reputation: 53
One way to hack limited form of polymorphism in C is to do something like this:
typedef struct {
int x;
} base;
typedef struct {
base super;
int y;
} derived;
Now you can refer to a derived instance as a base instance, depending on how the variable is cast, ie:
derived my_derived;
my_derived.y = 10;
my_derived.super.x = 20;
//will print 10
printf("%d", (&my_derived)->y);
//will print 20
printf("%d", ((base*)(&my_derived) )->x);
So my question is, how exactly does this work? Is it because when you cast it as base and referencing a variable, you're referencing the int member 'x' as the offset from the start of the 'base' struct? This is the only thing I can think of, any help would be appreciated.
Thanks alot!
Upvotes: 5
Views: 266
Reputation: 101
An struct is a byte area memory that the compiler knows his structure, that is, what variables you declare inside.
For example you can declare a struct:
struct st {
int number;
};
struct st n;
n.number = 10;
printf("n=%i\n", n.number);
But you can change the compiler behaviour, for example declare a pointer to char over your struct:
char *c = (char*)&n;
printf("char c=%c\n", c[0]);
This is a legal declaration. Then you can change in any moment the structure of that memory zone. The only important thing is the memory address of your declared struct.
In your example, when declare the derived structure the program reserve a memory area to allocate the derived stucture, but the form that the compiler see this area can be changed any time:
struct derived my_derived;
struct base *b = (struct base*)&my_derived;
b->x = 20;
my_derived.y = 10;
printf("x=%i y=%i\n", my_derived.base.x, my_derived.y);
In this case b and &my_derived share the same memory area, you only change how the compiler "see" this area.
The use of the "type punning" is the base of the oop heritage simulation in C, an non oop programming langugage.
I use this technique in my projects: oop4c
Upvotes: 2
Reputation: 355187
In a struct, there can be unnamed padding bytes between data elements or at the end of the struct, but not at the beginning. So, the address of the first data element of a struct-type object is guaranteed to be the same as the address of the struct-type object itself.
So, in your example, the address of my_derived
is the same as the address of my_derived.super
.
Upvotes: 11
Reputation: 14086
Is it because when you cast it as base and referencing a variable, you're referencing the int member 'x' as the offset from the start of the 'base' struct?
Yes. This technique is sometimes referred to as "type punning".
This is used in the POSIX standard library; for example, in struct sockaddr. Usually you declare one as a sockaddr_storage, pass it around as a sockaddr, and manipulate it as a sockaddr_in or _in6 depending on what kind of address is actually stored inside it.
Upvotes: 1