Struct Inheritance in C
Asked Answered
E

12

85

Can I inherit a structure in C? If yes, how?

Emlyn answered 11/7, 2009 at 18:28 Comment(2)
There is no such thing as inheritance in CPlumb
...Unless you implement it.Drear
H
111

The closest you can get is the fairly common idiom:

typedef struct
{
    // base members

} Base;

typedef struct
{
    Base base;

    // derived members

} Derived;

As Derived starts with a copy of Base, you can do this:

Base *b = (Base *)d;

Where d is an instance of Derived. So they are kind of polymorphic. But having virtual methods is another challenge - to do that, you'd need to have the equivalent of a vtable pointer in Base, containing function pointers to functions that accept Base as their first argument (which you could name this).

By which point, you may as well use C++!

Handmaid answered 11/7, 2009 at 18:34 Comment(8)
Well, that's assuming a C++ compiler is available for your platform!Kohler
If a C compiler is available, then so is a C++ compiler - just use one that produces C as its output.Handmaid
Phew.. you saved my life. I usually code in Java and when faced with the code similar to what you posted I thought it's a composition and was confused like hell when they cast it.Upmost
You san see uses of that in complex real projects (like git). See log-tree.c. struct tag "inherits" from struct objectWhorl
I used to do the exact same thing. Problem is, if you implement the indirection of "methods", you'll get pretty bad readability, like personDAO->getPersonById(personDAO, personDTO->getId(personDTO));. Unfortunately, I don't see another way around.Lobachevsky
Excuse me, how is this Base *b = (Base *)d; possible. One is a Derived* and the other is a Base*. Since Derived would have more members, the pointers are different. How can you do that cast?Lebbie
@DogaOruc C is so low-level that the pointer is just that, a pointer. It has no type. As the answer says, if a pointer location starts with the contents of Base, it can sort of be used as a Base. In this case, the memory location of a Derived begins exactly the same as the memory location of a Base. (There may be errors in this comment, this is my interpretation.)Thieve
@Thieve C is so low-level that the pointer is just that, a pointer. It has no type. That is absolutely not true. And common initial sequences are restricted to members of the same union. This answer is wrong. "As Derived starts with a copy of Base, you can do this: Base *b = (Base *)d;" No, you can't safely do that. See #34616586Internist
K
49

C has no explicit concept of inheritance, unlike C++. However, you can reuse a structure in another structure:

typedef struct {
    char name[NAMESIZE];
    char sex;
} Person;

typedef struct {
    Person person;
    char job[JOBSIZE];
} Employee;

typedef struct {
    Person person;
    char booktitle[TITLESIZE];
} LiteraryCharacter;
Kohler answered 11/7, 2009 at 18:34 Comment(4)
As far as I know, you can have a struct/class member inside another in C++ as well.Goddess
C says that no padding appears before the first member of a struct. So you can in fact (and are allowed) cast LiteraryCharacter* to Person*, and treat it as a person. +1Bottomry
@JohannesSchaub-litb your comment was a better explanation than the answer itself :)Goldwin
It is important to note that you must only pass these types be reference. You can't copy them into a Person object or it will splice.Sperling
O
42

I like and used the idea of Typesafe inheritance in C.

For example:

struct Animal
{
    int weight;
};

struct Felidae
{
    union {
      struct Animal animal;
    } base;
    int furLength;
};

struct Leopard
{
    union {
      struct Animal animal;
      struct Felidae felidae;
    } base;

    int dotCounter;
};

Usage:

struct Leopard leopard;
leopard.base.animal.weight = 44;
leopard.base.felidae.furLength = 2;
leopard.dotCounter = 99;
Opposable answered 5/10, 2011 at 9:21 Comment(7)
I never thought of this. And if you make the union anonymous it is quite tidy. However the downside is that you need to list all parents in order to avoid nested variables.Sperling
Interesting approach. However, as soon as you type leopard.base, the whole point of inheritance/polymorphism is eliminated.Lobachevsky
Don' you get a diamond inheritance problem here? leopard.base.felidae.base.animal.weight and leopard.base.animal.weight?Tacet
@Alexander Torstling no, you won't. leopard.base.felidae.base.animal.weight is just another name for leopard.base.animal.weight - it is the same thing at the same place in memory.Opposable
This looks great. Thanks.Myself
I'd argue that in some cases, you don't want polymorphic behavior, just the inherited abilities. However, you could still get polymorphism as every thing based on Animal should have a .base.animal namespace, although, it's a bit contrived.Gomel
You'll run into problems if someone decides to cast the struct into a pointer of its initial member though, which C claims to be well-defined. And it would, if we use the correct type for the initial member. But *(struct Animal*)&leopard... oops, the first member is not a struct Animal but a union. And now we end up deep into the dark woods were language lawyers stalk and dangerous beasts lurk, such as strict aliasing, the common initial sequence rule or composite type rules. That's not a safe place to be. Maybe cook up something with _Generic or typeof instead.Leafy
A
11

If your compiler supports anonymous structs, you can do this:

typedef struct Base
{
    // base members
} Base_t;

typedef struct
{
   struct Base;  //anonymous struct

   // derived members

} Derived_t;

This way, base stuct members can be acessed directly, which is nicer.

Alcinia answered 26/8, 2012 at 17:58 Comment(4)
The suffix _t is reserved in POSIX. Do whatever you want, just be advised that you're likely to run into conflicts if writing your code for a POSIX system (e.g. Linux) or someone eventually wants to port your code to a POSIX system.Charlenecharleroi
This doesn't actually work in standard C (not even C11).Whiskey
@Whiskey It does work, at least with GCC v12.2.0. Tried it with gcc -std=c11 -pedantic -Wall -Wextra main.c -o test and it worked fine.Dannettedanni
@Dannettedanni it does work but it isn't in the standard. See this answer. In short, it's a Microsoft extension and you should be passing -fms-extensions to your compiler, but modern GCC seems to enable it automatically.Cosenza
U
9

If you want to use some gcc magic (that I would assume would work with Microsoft's C compiler) you can do something like:


struct A
{
   int member1;
};

struct B
{
   struct A;
   int member2;
}

With gcc you can compile this with -fms-extensions (Allows for unnamed struct members like Microsofts compiler does). This is similar to the solution given by Daniel Earwicker except that it allows you to access memeber1 on a struct B instance. i.e B.member1 instead of B.A.member1.

This is probably not the most portable approach and will not work if using a C++ compiler (different language semantics mean that it is redeclaring/defining struct A instead of instantiating it).

If however you live in the gcc/C land only it will work and do exactly what you want.

Uneventful answered 13/5, 2013 at 13:50 Comment(9)
Isn't this composition?Allotment
Nope, it is proper inheritance. Assuming you have a struct of type struct B named b, b.member1 will compile and work as you would expect. Composition would be something like b.base.member1. GCC performs this magic for you. It actually has the definition of struct B as two integers in this case.Uneventful
Is this possible only in C, not in C++? If no, then please visit This.Allotment
It is only C. The syntax is illegal in C++. Although C++ has proper inheritance of structs just like classes.Uneventful
Now I am feeling much better!Allotment
Is that standard C or just GCC?Halftruth
It might be standardized in c11 I am not sure. It should work in msvc gcc and clang thoughUneventful
Describing this as "GCC magic" that might work in MSVC when it's copied from MSVC and requires the -fms-extensions flag to enable is very oddAbele
Hello, I can't make this work in GCC (compiler I used) even with -std=c11 flag, can I ask what flags did you use?Aureolin
K
4

You can do the above mentioned

typedef struct
{
    // base members

} Base;

typedef struct
{
    Base base;

    // derived members

} Derived;

But if you want to avoid pointer casting, you can use pointers to a union of Base and Derived.

Kirsti answered 11/7, 2009 at 19:5 Comment(0)
S
4

A slight variation to the answer of anon (and others' similar). For one level deep inheritance one can do the following:

#define BASEFIELDS              \
    char name[NAMESIZE];        \
    char sex

typedef struct {
    BASEFIELDS;
} Person;

typedef struct {
    BASEFIELDS;
    char job[JOBSIZE];
} Employee;

typedef struct {
    BASEFIELDS;
    Employee *subordinate;
} Manager;

This way the functions accepting pointer to Person, will accept pointer to Employee or Manager (with casting), same as in other answers, but in this case the initialisation will be natural as well:

Employee e = {
    .name = "...";
    ...
};

vs

# as in anon's answer
Employee e = {
    .person.name = "...";
    ...
};

I believe this is how some popular projects do that (eg. libuv)

UPDATE: also there are some good examples of similar (but not the same) concept in libsdl events implementation using structs and unions.

Seaport answered 23/2, 2016 at 22:58 Comment(5)
This has tricky aliasing consequences. As far as I understand, you can only recast across the inheritance chain as long as the structs are in dynamic memory. (See gustedt.wordpress.com/2016/08/17/effective-types-and-aliasing) Without dynamic memory, the union approach is probably better.Myself
@PSkocik Obviously care should be taken when playing with things like this as a lot of things can go wrong. I do believe the example will work for pointers to stack variables as well though. With that being said I do agree that when it comes to casting in scenarios like this one the Union is always a better solution.Seaport
It doesn't matter how the objects are allocated (automatic, static, dynamic). One example of potential problems is that different objects may have different amount of padding between name and sex fields, but there are others possibilities. The main issue is that there are no guarantees that this works, making this code unreliable, and thus unusable.Antiphonal
@Antiphonal I disagree with the padding statement. We only care about the BASEFIELDS and padding for those is deterministic on every given platform, provided they are always placed at the beginning of the struct. Obviously in real life scenario the BASEFIELDS will need to contain the type identifier to ensure proper treatment of the fields and things will get messy pretty soon. I agree though that this sort of hackery isn't nice and if one needs C++ style inheritance - they should use C++.Seaport
CPython also use syntax like this for constructing python Types see docs.python.org/2/extending/newtypes.html, here PyObject_HEAD defines the common fields required for python types.Appetitive
A
4

This works compiling with -fms-extensions

Diagram image

main.c

#include "AbstractProduct.h"
#include "Book.h"
#include "Product.h"
#include "TravelGuide.h"

/***********************/

int main() {

    Product p = Product_new();  
    p.set_id(&p, 2);
    p.set_name(&p, "name2");
    p.set_description(&p, "description2");
    p.set_price(&p, 2000);  
    p.display(&p);

    TravelGuide tg = TravelGuide_new(); 
    tg.set_id(&tg, 1);
    tg.set_name(&tg, "name1");
    tg.set_description(&tg, "description1");        
    tg.set_price(&tg, 1000);
    tg.set_isbn(&tg, "isbn1");
    tg.set_author(&tg, "author1");
    tg.set_title(&tg, "title1");
    tg.set_country(&tg, "country1");
    tg.display(&tg);

}

AbstractProduct.c

#include "AbstractProduct.h"

/*-------------------------------*/

static void set_id(AbstractProduct *this, int id) {
    this->id = id;
}

/*-------------------------------*/

static void set_name(AbstractProduct *this, char *name) {
    strcpy(this->name, name);
}

/*-------------------------------*/

static void set_description(AbstractProduct *this, char *description) {
    strcpy(this->description, description);
}

/*-------------------------------*/

static int get_id(AbstractProduct *this) {
    return this->id;    
}

/*-------------------------------*/

static char *get_name(AbstractProduct *this) {
    return this->name;  
}

/*-------------------------------*/

static char *get_description(AbstractProduct *this) {
    return this->description;   
}

/*-------------------------------*/

static void display(AbstractProduct *this) {

    printf("-AbstractProduct- \n"); 
    printf("id: %d\n", this->get_id(this)); 
    printf("name: %s\n", this->get_name(this)); 
    printf("description: %s\n", this->get_description(this));   
    printf("\n");
}

/*-------------------------------*/

void AbstractProduct_init(AbstractProduct *obj) {

    obj->set_id = set_id;
    obj->set_name = set_name;
    obj->set_description = set_description; 
    obj->get_id = get_id;
    obj->get_name = get_name;
    obj->get_description = get_description;
    obj->display = display;

}

/*-------------------------------*/

AbstractProduct AbstractProduct_new() {

    AbstractProduct aux;
    AbstractProduct_init(&aux);
    return aux;
}

AbstractProduct.h

#ifndef AbstractProduct_H
#define AbstractProduct_H

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

/***********************/

typedef struct AbstractProduct{

    int id;
    char name[1000];
    char description[1000];

    void (*set_id)();
    void (*set_name)();
    void (*set_description)();  
    int (*get_id)();    
    char *(*get_name)();    
    char *(*get_description)(); 
    void (*display)();  

} AbstractProduct;

AbstractProduct AbstractProduct_new();
void AbstractProduct_init(AbstractProduct *obj);

#endif

Book.c

#include "Book.h"

/*-------------------------------*/

static void set_isbn(Book *this, char *isbn) {
    strcpy(this->isbn, isbn);
}

/*-------------------------------*/

static void set_author(Book *this, char *author) {
    strcpy(this->author, author);
}

/*-------------------------------*/

static void set_title(Book *this, char *title) {
    strcpy(this->title, title);
}

/*-------------------------------*/

static char *get_isbn(Book *this) {
    return this->isbn;  
}

/*-------------------------------*/

static char *get_author(Book *this) {
    return this->author;    
}

/*-------------------------------*/

static char *get_title(Book *this) {
    return this->title; 
}

/*-------------------------------*/

static void display(Book *this) {

    Product p = Product_new();
    p.display(this);    

    printf("-Book- \n");
    printf("isbn: %s\n", this->get_isbn(this)); 
    printf("author: %s\n", this->get_author(this)); 
    printf("title: %s\n", this->get_title(this));   
    printf("\n");
}

/*-------------------------------*/

void Book_init(Book *obj) {

    Product_init((Product*)obj);

    obj->set_isbn = set_isbn;
    obj->set_author = set_author;
    obj->set_title = set_title; 
    obj->get_isbn = get_isbn;
    obj->get_author = get_author;
    obj->get_title = get_title; 
    obj->display = display;
}
/*-------------------------------*/

Book Book_new() {

    Book aux;   
    Book_init(&aux);
    return aux;
}

Book.h

#ifndef Book_H
#define Book_H

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "Product.h"

/***********************/

typedef struct Book{

    Product;
    char isbn[1000];
    char author[1000];
    char title[1000];

    void (*set_isbn)();
    void (*set_author)();
    void (*set_title)();    

    char *(*get_isbn)();
    char *(*get_author)();
    char *(*get_title)();   
    // void (*display)();   


} Book;

Book Book_new();
void Book_init(Book *obj);

#endif

Product.c

#include "Product.h"

/*-------------------------------*/

static void set_price(Product *this, double price) {
    this->price = price;
}

/*-------------------------------*/

static double get_price(Product *this) {
    return this->price; 
}

/*-------------------------------*/

static void display(Product *this) {

    AbstractProduct p = AbstractProduct_new();
    p.display(this);    

    printf("-Product- \n"); 
    printf("price: %f\n", this->get_price(this));   
    printf("\n");
}

/*-------------------------------*/

void Product_init(Product *obj) {

    AbstractProduct_init((AbstractProduct*)obj);

    obj->set_price = set_price;
    obj->get_price = get_price; 
    obj->display = display;

}

/*-------------------------------*/

Product Product_new() {

    Product aux;    
    Product_init(&aux);
    return aux;
}

Product.h

#ifndef Product_H
#define Product_H

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "AbstractProduct.h"

/***********************/

typedef struct Product{

    AbstractProduct;
    double price;

    void (*set_price)();
    double (*get_price)();  
    // void (*display)();   

} Product;

Product Product_new();
void Product_init(Product *obj);

#endif

TravelGuide.c

#include "TravelGuide.h"

/*-------------------------------*/

static void set_country(TravelGuide *this, char *country) {
    strcpy(this->country, country);
}

/*-------------------------------*/

static char *get_country(TravelGuide *this) {
    return this->country;   
}

/*-------------------------------*/

static void display(TravelGuide *this) {

    Book b = Book_new();
    b.display(this);

    printf("-TravelGuide- \n"); 
    printf("country: %s\n", this->get_country(this));   
    printf("\n");
}

/*-------------------------------*/

void TravelGuide_init(TravelGuide *obj) {

    Book_init((Book*)obj);
    obj->set_country = set_country;
    obj->get_country = get_country;
    obj->f = obj->display;
    obj->display = display;

}

/*-------------------------------*/

TravelGuide TravelGuide_new() {

    TravelGuide aux;
    TravelGuide_init(&aux);
    return aux;
}

TravelGuide.h

#ifndef TravelGuide_H
#define TravelGuide_H

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "Book.h"

/***********************/

typedef struct TravelGuide{

    Book;
    char country[1000];
    void (*f)();

    void (*set_country)();
    char *(*get_country)();
    // void *(*display)();

} TravelGuide;

TravelGuide TravelGuide_new();
void TravelGuide_init(TravelGuide *obj);

#endif

Makefile

.PHONY: clean
define ANNOUNCE_BODY

    ***********************************************
    ************          start make **************
    ***********************************************
endef

all:
    $(info $(ANNOUNCE_BODY))    

    clear;
    if [ -f binary/main ]; then rm binary/main; fi;

# compiler 

    gcc $(INC) -c -fms-extensions main.c -o binary/main.o
    gcc $(INC) -c -fms-extensions AbstractProduct.c -o binary/AbstractProduct.o
    gcc $(INC) -c -fms-extensions Product.c -o binary/Product.o
    gcc $(INC) -c -fms-extensions Book.c -o binary/Book.o
    gcc $(INC) -c -fms-extensions TravelGuide.c -o binary/TravelGuide.o

# linker    

    gcc binary/main.o \
        binary/AbstractProduct.o \
        binary/Product.o \
        binary/Book.o \
        binary/TravelGuide.o \
        -o \
        binary/main
Assured answered 13/3, 2016 at 1:45 Comment(4)
nice for proof of concept, but if you have to go all this way to achieve OOP in C, you'd be better off with C++. IMHO C is not designed for OOP and if it really needs to be pure OOP (with a lot of effort spent to achieve that), you're using the wrong language. I for one wouldn't want to maintain such code.Seaport
Also, with this approach I think you're wasting some memory. Unlike C++ there will be additional sizeof(function pointer) * number of methods for each instance. In C++ sizeof(class) does not include the method pointers and in case there are virtual methods - contains 1 additional pointer to vtable.Seaport
Calling methods with "." makes it easier, and it doesn't pollute the name space. Separating new and init makes a lot of sense. It is the best answer, I don't know how it's so low.Blubber
Can you please shorten your answer?Electroshock
Q
-3

C is not an object-oriented language and hence has no inheritance.

Quiberon answered 11/7, 2009 at 18:30 Comment(0)
S
-3

You can simulate it, but you can't really inherit.

Stratus answered 11/7, 2009 at 18:30 Comment(1)
what's reality? C++ is just a very simple runtime library for dispatching and a lot of compiler syntax to call it when needed. the original C++ compilers produced C code, after all. (and very readable C in fact)Roan
A
-4

No, you cant. imo the best approach to OOP in C is using ADT.

Acetify answered 11/7, 2009 at 18:34 Comment(1)
You can, but yes, the ADT pattern is the best pattern in C for this type of work.Gunnery
A
-6

No you cannot. C does not support the concept of inheritance.

Altostratus answered 11/7, 2009 at 18:30 Comment(1)
doesn't support but doesn't get in the way either.Roan

© 2022 - 2024 — McMap. All rights reserved.