0

Suppose we have the following struct in C:

struct student {
    int AM;
    char name[50];
    char surname[50];
    char course[50];
};

How can someone implement retrieve(R, c, s) and update(R, c, s).

  • In retrieve() R is a pointer to a struct student, s is a field of the struct and c will have the value of this field.
  • In update() R is again a pointer to a struct student, s is a field of the struct and in the end the field of the struct will have the value of c.
Mario
  • 35,726
  • 5
  • 62
  • 78
kiriakosv
  • 11
  • 1
  • 6
  • 3
    Is this some homework or course stuff to do? What have you tried so far? There are multiple possible approaches to this, but we don't know what you know already. Also it's not really that great to just copy and paste everything from somewhere, so you should still try to (and show that) you're doing your own part. Worst case your solution will be buggy, but it's still better than just copying the 100% perfect solution. – Mario Feb 17 '14 at 10:20
  • The main problem I have is that in retrieve I can't predefine what will be the type of the variable c because I don't know what will be the field. I tried by setting c to void and then cast it inside the function accordingly but it didn't worked – kiriakosv Feb 17 '14 at 10:26
  • So `AM` would be another possible field to return? – Mario Feb 17 '14 at 10:27
  • Yes, if all the fields were of the same type I could treat it like an array. – kiriakosv Feb 17 '14 at 10:29
  • @kir2: No, you chouldn't, because compilers pad structs. The offset of the members is unpredictable – Elias Van Ootegem Feb 17 '14 at 10:37

3 Answers3

1

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

Check working codepad here

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
    );
}
Community
  • 1
  • 1
Elias Van Ootegem
  • 74,482
  • 9
  • 111
  • 149
1

Since you're limited to using C, your options are rather limited.

Basically, there are two different options (assuming you don't want to dive into memory/pointer magic):

  • Create alternative functions to set/retrieve your data based on the actual type.
  • Create your own "variant" datatype as a struct.

Using different functions, you could do something like this:

void retrieveInteger(const struct student *dataset, const char *field, int *value) {
    if (!dataset || !field || !value) // some minimal error checking
        return;
    if (!strcmp(field, "AM")) {
        *value = dataset->AM;
        return;
    }
    printf("Tried to retrieve unknown integer field '%s'!\n", field);
}

Retrieving strings or setting integers would work in a similar way using their own functions.

Using a custom variant datatype:

struct variant {
    char type;
    union {
        char string[50];
        int integer;
    };
};

void retrieve(const struct student *dataset, const char *field, struct variant *value) {
    if (!dataset || !field || !value) // some minimal error checking
        return;
    if (!strcmp(field, "AM")) {
        value->type = 0; // let's assume this represents an integer value
        value->integer = dataset->AM;
        return;
    }
    printf("Tried to retrieve unknown integer field '%s'!\n", field);
}

int varToInt(struct variant *value) {
    if (value->type == 0)
        return value->integer;
    else // Not an integer value
        return 0;
}
Mario
  • 35,726
  • 5
  • 62
  • 78
  • _"assuming you don't want to dive into memory/pointer magic"_ => The OP wants to assign a value of a struct member to the third argument of the function. I'd say you _have_ to assume pointer arithmetic is the only valid way to tackle this problem. Your answer may work, but it relies on hard-coding the members of the struct. If you keep steering clear of pointer magic, then perhaps you have to leave C alone until you're ready to learn to love pointers... – Elias Van Ootegem Feb 17 '14 at 11:22
  • True, but at the same time the basic promise of this task is quite obsolete (as you said: "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."). I think this is some basic task/assignment to include working with pointers, structs, switch statements, etc. While your answer is perfectly valid I think you might be "overcomplicating" things a bit to get a generic implementation that doesn't rely on hardcoding things like member names. – Mario Feb 17 '14 at 12:35
  • And yeah, "pointer magic" might be a bit off, more like "avoiding pointer arithmetic based on data structure alignment". – Mario Feb 17 '14 at 12:35
  • I think the solution has to do with offsets..I know about pointers but not about offsets. – kiriakosv Feb 17 '14 at 14:31
  • If you want to work with offsets, look at Elias' answer. Don't just compare the parameters and then calculate the offsets by hand. – Mario Feb 18 '14 at 10:11
0

You're asking the wrong question, rather than trying to find strict offsets, what you really want to do is to use a mapping datatype (a.k.a. dictionary). This could be a simple array whose members you could access using constants:

#define STUDENT_ID 0
#define STUDENT_NAME 1
#define STUDENT_SURNAME 2
#define STUDENT_COURSE 3

typedef void** student;

void update(student R, void* c, size_t s) {
    R[s] = c;
}
void* retrieve(student R, size_t s) {
    return R[s];
}

// you might want to define some helper macros
#define update_dynamic(type, R, c, s) { \
    type* ptr = malloc(sizeof(type)); \
    *ptr = c; \
    update(R, ptr, s); \
}
#define retrieve_dynamic(type, R, c, s) { \
    type* ptr = (type*) retrieve((R), (s)); \
    c = *ptr; \
    free(ptr); \
}


struct student s1 = malloc(4 * sizeof(void*));
int* id = malloc(sizeof(int));
id = 12;
update(s1, id, STUDENT_ID);

Or you could go ahead and use a hash table or tree-based mapping data structure so you could use strings for the field names.

Lie Ryan
  • 62,238
  • 13
  • 100
  • 144