Casting a void pointer in struct

135 views Asked by At

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));
4

There are 4 answers

0
Scott Mercer On BEST ANSWER

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

3
dbush On

What you want is a union:

typedef struct Device
{
    struct SomeData *data;
    union {
        struct SysInfo_S s;
        struct SysInfo_P p;
    } sysInfo;
    ...
} Device;
9
Jason On

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;
3
Peter - Reinstate Monica On

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");
  }
}