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
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 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.
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.
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With