2

Here is what I got ..

typedef struct SysInfo_P
{
    int some_P_int;
    char some_P_char[10];
    ...

} SysInfo_P;


typedef struct SysInfo_S
{
    int some_S_int;
    char some_S_char[10];
    ...

} SysInfo_S;


typedef struct Device
{
    struct SomeData *data;
    void *sysInfo;
    ...
} Device;

The idea is to have a base class Device, and cast its void pointer to the appropriate SysInfo_x class during allocation. I use void pointers all the time to provide versatility to common functions, but I can't seem to wrap my head around this ... perhaps it cannot be done.

Here is my futile attempt at casting it .. in my allocator.

self->sysInfo = (SysInfo_S *)malloc(sizeof(*self->properties->sysInfo));
memset(self->sysInfo, 0, sizeof(*self->properties->sysInfo));
5
  • Oh I see. It won't work like that. void* will erase type information. You cannot dereference it without a cast first. You could do self->sysInfo = malloc(sizeof SysInfo_S); .. not sure if that does what you want though. Commented Jan 25, 2024 at 16:51
  • 1
    Yeah well, you cannot erase a type and still use it ;-). Either-or. To me it looks as if you could work with a union and an enum "tag" variable memorizing which kind of data is there; either a union of two pointers or even a union of the two structs. Commented Jan 25, 2024 at 16:55
  • Also: I suppose the two structs are in real life not identical? Commented Jan 25, 2024 at 16:56
  • I have also seen people do "poor man's inheritance" with a common part at the beginning of a struct (which could include the type info); the latter part varies between "derived types". It is always safe to cast to the first part from a void pointer (because we know that's there), and then branch to different casts and the corresponding processing depending on the actual "derived" type. ghostscript does that excessively with device drivers which all share a common interface. If you do that you can omit the extra "tag" variable; it is always at the beginning of each struct. Commented Jan 25, 2024 at 16:58
  • that is correct, the structs are not identical, all SysInfo_x structs contain device specific data which, unfortunately, is not Identical. If they were, I'd be long done this project Commented Jan 25, 2024 at 17:02

4 Answers 4

4

What you want is a union:

typedef struct Device
{
    struct SomeData *data;
    union {
        struct SysInfo_S s;
        struct SysInfo_P p;
    } sysInfo;
    ...
} Device;
Sign up to request clarification or add additional context in comments.

3 Comments

Plus, usually, a type tag.
I accepted this answer because is it works and does exactly what i need.
@ScottMercer Then please click the "accept" marker. Comments to say so are not the way. ;-) It will help future visitors with the same issue find accepted answers right in the list of questions.
4

dbush's answer will work and is likely ideal. However, if you are trying to mimick OOP in C, you have it a bit reversed. Normally, the base becomes the first of member of the child class. This is because it is part of the standard that you are allowed to cast the address of a struct to a pointer of the type of the first of member. So something like this:


typedef struct Device
{
    struct SomeData *data;
    ...
} Device;

typedef struct SysInfo_P
{
    Device base;
    int some_P_int;
    char some_P_char[10];
    ...
} SysInfo_P;


typedef struct SysInfo_S
{
    Device base;
    int some_S_int;
    char some_S_char[10];
    ...
} SysInfo_S;

Then, you can legally do things like this:

SysInfo_P* sysinfo = malloc(sizeof(*sysinfo));
Device* parent = (Device*)sysinfo;

9 Comments

Remove the & in Device* parent = (Device*)&sysinfo;. (sysinfo is a pointer already.)
This was the missing piece of my puzzle in OOP in C.
@ScottMercer Yeah, this is how libraries like GTK do it. This is "inheriting" the base class. As Peter has already mentioned, if you keep an enum in the base that tells it what child type it is, then you can legally cast the parent back to the child as well.
Remove ugly and dangerous cast by replacing (Device*)sysinfo with&sysinfo->base.
@tstanisl Given that "it is part of the standard that you are allowed to cast the address of a struct to a pointer of the type of the first of member", what would be danger of having the cast there? I got the impression Jason used the cast to emphasise its legality.
|
0

Just for completeness, here is a live example. The key is to use the type tag enum to select the appropriate functions for each info type (like the following print functions) which would be virtual members in C++:

enum SysInfoE { S, P }; // one for each type

// print a SysInfo_S
void print_S(void* vp)
{
  SysInfo_S* sp = (SysInfo_S *)vp;
  printf("some int: %d, ", sp->some_S_int);
  for (unsigned int i = 0; i < sizeof sp->some_S_char; i++)
  {
    printf("char[%u]: %d, ", i, sp->some_S_char[i]);
  }
}

// print a SysInfo_P
void print_P(void* vp)
{
  SysInfo_P* pp = (SysInfo_P*)vp;
  printf("some int: %d, ", pp->some_P_int);
  for (unsigned int i = 0; i < sizeof pp->some_P_float / sizeof *pp->some_P_float; i++)
  {
    printf("float[%u]: %f, ", i, pp->some_P_float[i]);
  }
}

// A global array of function pointers. Index with S or P.
void (*pr_func[])(void*) = { print_S, print_P };


void print_dev(Device* dp)
{
  printf("dev type: %d\n", dp->InfType);
  pr_func[dp->InfType](&dp->sys_info); // use proper function in array, pass ptr to union
}


This "infrastructure" would be in a different translation unit, possibly in a library. The calling code is in a different file and looks like this:


int main()
{
  char s_data[] = "123456789"; // plus 0 byte
  const float p_data[] = {10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 }; 

  Device devices[2] = { { S }, { P } };
  memcpy(devices[0].sys_info.s.some_S_char, s_data, 
    sizeof devices[0].sys_info.s.some_S_char);
  memcpy(devices[1].sys_info.p.some_P_float, p_data, 
    sizeof devices[1].sys_info.p.some_P_float / sizeof *devices[1].sys_info.p.some_P_float);
  
  for( unsigned int i= 0; i < sizeof devices / sizeof *devices; i++)
  {
    // uniform handling, code does not need to know different sysinfo types
    print_dev(devices+i);
    printf("\n");
  }
}


3 Comments

As pointed out in another answer, if the purpose was to create inheritance, then the OP got it all backwards. The base class should know nothing of who might inherit it, nor should you need an enum. If the derived classes contain the base class as first object, the derived class could from its constructor setup function pointers in the base class object. Or if carrying around a bunch of function pointers is too heavy memory-wise, have the derived class setup a vtable index in the base class.
@Lundin Yes, you could go full interface/implementations and only define "virtual" function pointers in the base class; but "abstracting away" all data that way may not be suitable for all C use cases. As soon as you expose data (usually in a union or behind a void pointer), you'll usually need a type tag, don't you?
great example .. more info for the tool box
0

So since there is so much good info in this thread, i thought I should clarify the question, and the what I was trying to Achieve and why.This require more context...

The project is and IOT library written in C. Since it must support many types of devices, all just a little bit different, naturally an OOP approach was best suited.

So at first i used a quick and dirty solution,include pointers to both types, then allocate only the necessary type during constructing. This works great if you don"t mind wading through extra pointers to nowhere in your code editor when including library .. so really, the question wasn't so much about how to do it, but more so, about how to do it in a clean way. The first answer i accepted was more-less just a better (more memory efficient way) of doing what i was already doing. I still ended up with the same issue, all device, methods..sysinfo .etc pollute device/Object specific properties and methods.

the key to my solution was the second answer i accepted, including the base class as the first member of the derived classes.

As mentioned, the library must interact with many device types, so my solution was to give each type it own class, then use the base class to access the child from with in functions, since the devices are all very closely related this works very well and eliminated any need for void pointers.

here was the solution to my problem ...

typedef struct SysInfo_P
{
    Device base;
    int some_P_int;
    char some_P_char[10];
    ...
} SysInfo_P;


typedef struct SysInfo_S
{
    Device base;
    int some_S_int;
    char some_S_char[10];
    ...
} SysInfo_S;

 typedef struct Device_S
{

    Device base;
    SysInfo_S *sysinfo;

} Device_S;

 typedef struct Device_P
 {

     Device base;
     SysInfo_P *sysinfo;

} Device_P;

typedef struct Device
{
    uint8_t type;
    uint8_t device_index;
    uint8_t type_index;
} Device;

This way, i can just pass the base class around, and used the type and type_index to access the child directly, from within. Perfect!!

hopefully t this thread saves someone a great deal of time .. i spent many hours looking for the right solution. Because of the answer and few key tips from @Jason this library is done and exceeds all criteria. Thank again to everyone and big thanks to S.O fora site full of such a great information and knowledgeable members. Cheers

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.