Inheritance in plain c

A77e71b962cd6c7c3b885f0488452f1f
0
tobeythorn 101 Feb 26, 2011 at 05:09

Hi,
I really do like the relative simplicity of plain c and recently have become curious about doing some basic object oriented things in plain c such as inheritance. I have read about how type punning (casting a pointer to different types) is not strictly allowed by the c standard and may cause serious bugs with certain compilers or compiler options, and that using unions tricks can be similarly problematic. The obvious solution to me is the following, for example:

#include <stdio.h>
#include <stdlib.h>
struct person;
struct cowboy;
typedef struct person Person;
typedef struct cowboy Cowboy;
struct person {char* greeting;};
struct cowboy {Person super; char* greeting;};

void Person_init(Person* aPerson)
{
    aPerson->greeting = "Hello";
}

void Cowboy_init(Cowboy* aCowboy)
{
    Person_init(&(aCowboy->super));
    aCowboy->greeting = "Howdy";
}

void main()
{
    Cowboy myCowboy;
    Cowboy_init(&myCowboy);

    printf("myCowboys greeting: %s\n", myCowboy.greeting);
    printf("myCowboys superclass greeting: %s\n", myCowboy.super.greeting);

    printf("Address of myCowboy: %d\n", &myCowboy);
    printf("Address of myCowboys superclass: %d\n", &(myCowboy.super));
    return 0;
}

Output:
myCowboy’s greeting: Howdy
myCowboy’s superclass greeting: Hello
Address of myCowboy: 2686744
Address of myCowboy’s superclass: 2686744

What seems strange to me though is that while this doesn’t seem to violate the c standard, just like type punning, &(aCowboy->super) does return the same address as &myCowboy, but as a different pointer type. It therefor is hard for me to believe that type punning is any less correct than my solution OR that my solution is any more correct than type punning (and how could my solution not be correct?)

I’d appreciate your thoughts on this matter,
Thanks,
Tobey

4 Replies

Please log in or register to post a reply.

A8433b04cb41dd57113740b779f61acb
0
Reedbeta 168 Feb 26, 2011 at 05:37

I can’t speak to what the standard says (.oisyn will no doubt weigh in on it :)), but I know the Windows API, for one, relies heavily on “type punning” e.g. to be able to extend a structure with new members in a later version of Windows.

And of course, the memory layout of the cowboy struct in your example is just what a typical C++ compiler is doing internally when you have cowboy derived from person. It’s a pretty safe bet this sort of thing will work on any of the standard PC/Mac platforms. (I wouldn’t care to speculate what might happen on some oddball embedded platform or something like that…but I guess PC/Mac is likely what you care about anyway…)

2b97deded6213469bcd87b65cce5d014
0
Mihail121 102 Feb 26, 2011 at 10:36

Ah, thank you for making me look in my favorite document, the C1X draft.

I have read about how type punning (casting a pointer to different types) is not strictly allowed by the c standard and may cause serious bugs with certain compilers or compiler options…

You can safely convert any pointer to an object type to a pointer to another object type (I suppose this is what you need). You can safely convert pointers to integers, but the implementation defines the semantics of this conversion. You can safely convert integers to pointers, but the result is defined by the specific implementation. You can convert a pointer to a pointer of a character type which allows you to access the individual bytes of the representation.

For more details see section 6.3.2.3 (Pointers) of the standard.

340bf64ac6abda6e40f7e860279823cb
0
_oisyn 101 Feb 26, 2011 at 12:16

@Reedbeta

I can’t speak to what the standard says (.oisyn will no doubt weigh in on it ;))

I woke up this morning with a sudden strong feeling that my help was needed somewhere in this universe. :)

Anyway, the person struct and the person-part of cowboy are layout compatible. You can safely convert a cowboy* to a person* and access the person’s members. Basically, if you recursively “unfold” each struct definition into their members, and you put the types of all the members in a list, two structs are layout compatible up until a certain point if the lists are equivalent up until that point.

So, person is a { char* }, cowboy is a { char*, char* }, therefore person and cowboy are layout-compatible for the first char*.

A77e71b962cd6c7c3b885f0488452f1f
0
tobeythorn 101 Feb 26, 2011 at 15:13

Thanks for your replies.
So, it looks like assuming the c1x standard, there won’t be any issues (and probably is in most compilers already), using either method.