Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use a base struct to extract values from void*

Tags:

c

struct

I have many types of structs in my project, and another struct that holds a pointer to one of these structs. Such as,

struct one{int num = 1;};
struct two{int num = 2;};
struct three{int num = 3;};

// These structs hold many other values as well, but the first value is always `int num`.

And I have another struct that holds references to these structs. I had to use void* because I do not know which of these structs is going to be referenced.

struct Holder{void* any_struct};

My question is, I need the values inside these structs, but I have a void pointer, could I declare a base struct that the first variable is an int, cast it, and use it to extract the num variable from these structs, such as:

struct Base{int num};
((Base*) Holder->any_struct)->num
// Gives 1, 2 or 3
like image 228
Max Paython Avatar asked Oct 31 '19 10:10

Max Paython


People also ask

What does struct mean in C++?

Structures (also called structs) are a way to group several related variables into one place. Each variable in the structure is known as a member of the structure. Unlike an array, a structure can contain many different data types (int, string, bool, etc.).

How do you declare a struct in C++?

How to declare a structure in C++ programming? The struct keyword defines a structure type followed by an identifier (name of the structure). Then inside the curly braces, you can declare one or more members (declare variables inside curly braces) of that structure.

Can a structure member be a void type?

Now ask yourself the same questions. There is no illegal void member, you meant void* . void is an incomplete type, and the only possible members of incomplete types allowed in union and struct (as last member) are incomplete array types of complete types.

How do you access a structure variable using pointer?

To access members of a structure using pointers, we use the -> operator. In this example, the address of person1 is stored in the personPtr pointer using personPtr = &person1; . Now, you can access the members of person1 using the personPtr pointer.


3 Answers

If the only thing you need is to extract the num, you can use memcpy. Assuming it's always int and always first and always present.

int num = 0;
memcpy(&num, Holder->any_struct, sizeof(int));
// Gives 1, 2 or 3 in num.

C99 standard section 6.7.2.1 bullet point 13:

A pointer to a structure object, suitably converted, points to its initial member. There may be unnamed padding within a structure object, but not at its beginning.

More info about the standard in this answer.

like image 128
EylM Avatar answered Nov 11 '22 17:11

EylM


I think this is acceptable, and I've seen this pattern in other C projects. E.g., in libuv. They define a type uv_handle_t and refer to it as a "Base handle" ... here's info from their page (http://docs.libuv.org/en/v1.x/handle.html)

uv_handle_t is the base type for all libuv handle types.

Structures are aligned so that any libuv handle can be cast to uv_handle_t. All API functions defined here work with any handle type.

And how they implement is pattern you could adopt. They define a macro for the common fields:

#define UV_HANDLE_FIELDS                                                      \
  /* public */                                                                \
  void* data;                                                                 \
  /* read-only */                                                             \
  uv_loop_t* loop;                                                            \
  uv_handle_type type;                                                        \
  /* private */                                                               \
  uv_close_cb close_cb;                                                       \
  void* handle_queue[2];                                                      \
  union {                                                                     \
    int fd;                                                                   \
    void* reserved[4];                                                        \
  } u;                                                                        \
  UV_HANDLE_PRIVATE_FIELDS                                                    \

/* The abstract base class of all handles. */
struct uv_handle_s {
  UV_HANDLE_FIELDS
};

... and then they use this macro to define "derived" types:

/*
 * uv_stream_t is a subclass of uv_handle_t.
 *
 * uv_stream is an abstract class.
 *
 * uv_stream_t is the parent class of uv_tcp_t, uv_pipe_t and uv_tty_t.
 */
struct uv_stream_s {
  UV_HANDLE_FIELDS
  UV_STREAM_FIELDS
};

The advantage of this approach is that you can add fields to the "base" class by updating this macro, and then be sure that all "derived" classes get the new fields.

like image 43
Darren Smith Avatar answered Nov 11 '22 19:11

Darren Smith


First of all, the various rules of type conversions between different struct types in C are complex and not something one should meddle with unless one knows the rules of what makes two structs compatible, the strict aliasing rule, alignment issues and so on.

That being said, the simplest kind of base class interface is similar to what you have:

typedef struct
{
  int num;
} base_t;

typedef struct 
{
  base_t base;
  /* struct-specific stuff here */
} one_t;

one_t one = ...;
...
base_t* ref = (base_t*)&one;
ref->num = 0; // this is well-defined

In this code, the base_t* doesn't point directly at num but at the first object in the struct which is of base_t. It is fine to de-reference it because of that.

However, your original code with the int num spread over 3 structs doesn't necessarily allow you to cast from one struct type to another, even if you only access the initial member num. There's various details regarding strict aliasing and compatible types that may cause problems.

like image 23
Lundin Avatar answered Nov 11 '22 19:11

Lundin