Side-notes:
I have to admit: I can't see the point in trying to write a function to update/retrieve a value from a struct in the way you describe. If you know the type of the struct, and you know what member you want to retrieve/update, what's wrong with str_var.member
or str_ptr->member
? It'll be more performant anyway...
The basic answer:
A basic example of how I might write a function that assigns to a member of a given struct would be this:
void update(void *str_ptr, size_t member, void * value, size_t v_size)
{
memcpy(
(void *) (((char *)str_ptr) + member),
value,
v_size);
}
How to call:
struct student your_struct;
const char *str = "string";
assign(
(void *) &your_struct,
offsetof(struct student, name),
(void *) str,
strlen(str)
);
The retrieve
function works similarly, but should be even easier to implement:
void retrieve( void *str_ptr, size_t member, void *target, size_t size)
{
if (size == 0) size = sizeof(*target);//optional
memcpy(
target,
(((char *) str_ptr) + member),
size
)
}
Which can be called like so:
int my_am;
retrieve ( (void *) &your_struct, offsetof(struct student, AM), &my_am, 0);
//or
retrieve ( (void *) &your_struct, offsetof(struct student, AM), &my_am, sizeof(my_am));
When retrieving strings, you may want to consider using strncpy
, and check the type of target
. But implementing that sort of thing is your job.
More efficient alternatives
Quite apart from the regular int foo = struct_var.member
, being the best option, as I explained at the top of my answer, it could well be you find yourself in a situation where that is not possible. My answer will work in those cases, but I've just provided 2 regular functions. There are better ways:
As you can see, though, these functions only add some syntactic sugar to your code, in the sense that they wrap a singular memcpy
call that would look a tad messy if left in-line:
memcpy(
(void *) &my_am,
(void *) (((char *) &str_ptr) + offsetof(struct student, am)),
sizeof(my_am)
);
So you could turn these functions into macro's or (if you're writing C99>= code), use inline
functions.
The choice is yours. Both the inline
and macro's have their pro's and cons.
Now, how does this all work?
Basically, the magic happens here:
(void *) (((char *)str_ptr) + member)
What happens here:
(char *)str_ptr
cast the void pointer to a char pointer. given that char
is guaranteed to be 1 byte in size, we can access struct members using pointer arithmetic now.
+ member
: this value was obtained through the offsetof
member, its value is the offset of a given member of a struct in respect to its initial memory address
(void *)
: re-cast the lot to a void pointer for the memcpy
function
That's it, really. If you can't predict the types you'll be working with, then simply use void *
The optional size = sizeof(*target);
bit is actually a way to "guess" the type with which you're dealing. If sizeof(*target);
is 1, there's a good chance you're dealing with a char type, but you shouldn't rely on this check, though...
More info
For more details on my usage of offsetof
and struct-member pointer arithmetic, check this question
The source, for future reference of a working implementation of these functions:
#include <stddef.h>
#include <stdio.h>
struct foo
{
int bar;
int foobar;
};
void retrieve( void *str_ptr, size_t member, void *target, size_t size);
void update( void *str_ptr, size_t member, void *value, size_t size);
int main ( void )
{
struct foo test = {.bar = 123,.foobar = 345};
int target = 0;
printf("Initial values: %d\n%d\n", target, test.foobar);
retrieve(
(void *) &test,
offsetof(struct foo, bar),
(void *) &target,
sizeof(target)
);
update (
(void *) &test,
offsetof(struct foo, foobar),
(void *) &target,
sizeof(target)
);
printf("After calls: %d\n%d\n", target, test.foobar);
return 0;
}
void retrieve( void *str_ptr, size_t member, void *target, size_t size)
{
//optional
if (size == 0) size = sizeof *target;
memcpy(
target,
(void *) (((char *) str_ptr) + member),
size
);
}
void update( void *str_ptr, size_t member, void *value, size_t size)
{
//optional
if (size == 0) size = sizeof(*target);
memcpy(
(void *) (((char *) str_ptr) + member),
value,
size
);
}