1

I have an assignment that is making me lose valuable hours without success. I have to read the contents from a struct that is passed to a function as void *c. I can read its contents without problems except the field that is a pointer to another struct. Example code:

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>

typedef struct Car Car;
struct Car {
        int     year;
        int     type;
        int     free;
        struct {
                char    *name;
                char    *city;
        } *facility;
};

int main()
{

    Car *c = malloc(sizeof(Car)*2);

    c->year = 2020;
    c->type = 2;
    c->free = 1;
    c->facility = malloc(200);
    c->facility->name = malloc(10);
    c->facility->city = malloc(10);
    snprintf(c->facility->name, 5, "Test");
    snprintf(c->facility->city, 5, "Test");
    test(c);
}

int test(void *c)
{
    int year = *(void **) c;
    int type = *(void **) (c+4);
    int free = *(void **) (c+4+4);
    printf("Year %d\n",year);
    printf("Type %d\n",type);
    printf("Free %d\n",free);
    void *facility;
    facility = *(void **) (c+4+4+4);
    printf("Facility name %s", *(void **) facility);
    printf("Facility city %s", *(void **) facility+8);
}

The part I am unable to read is facility name and facility city. I know I can access easily using -> but the assignment asks precisely understand how structure is defined in memory and extract its contents directly using the void*. Thanks.

Ivan
  • 23
  • 2
  • `c+4` This is illegal already, see [Pointer arithmetic for void pointer in C](https://stackoverflow.com/questions/3523145/pointer-arithmetic-for-void-pointer-in-c) for example. – dxiv Sep 21 '20 at 22:19
  • Why c+4 is illegal? Your link says is illegal because it is incomplete type, but in this case it isn't, then not illegal. Eugene, is not working. What I am trying is read the pointer stored in Car, then read the char * stored there, increment 8 because the previous pointer and then read next char * that is city. Thanks for voting negative... – Ivan Sep 21 '20 at 22:27
  • 1
    @Ivan It is illegal because `c` is declared as a `void *` and pointer arithmetic (incrementing, adding etc) is illegal for void pointers. Think at it, when you have an `int *p;` then `p + 1` points to the next `int`. The value of the pointer is actually increased by `sizeof int` which is the number of bytes an `int` occupies. But when you have a `void *p;` you don't know what `p` points to, or what size that *something* has, so it is meaningless to talk about `p + 1`. Just because gcc had it as an extension for a long time doesn't make it legal C, never was. P.S. That wasn't my downvote btw. – dxiv Sep 21 '20 at 22:35
  • @dxiv Yes I understand your point. But in this case does not apply. I know exactly where void * is pointing (the structure) and for that reason I increase by 4 (int) or 8 (pointer). Also this is an assignment (not production code), where I have to read the contents from the structure when the structure is passed as void *. I can tell my teacher is "illegal" and I am sure he will say, yes yes, you have a 0. Actually everything works except reading the facility struct inside Car . – Ivan Sep 21 '20 at 22:43
  • 1
    @Ivan `increase by 4 (int) or 8 (pointer)` My point is that `c + 4` only happens to "work" because of a compiler extension that you don't seem to even be aware that you are using. The code may give bogus results with other compilers, or fail to compile at all. The correct/portable way to write that would be `(char *)c + sizeof(int)`. – dxiv Sep 21 '20 at 22:47
  • 1
    @dxiv: Adding to a `void *` is what the C standard defines as *conforming* (a larger set than *strictly conforming*). The C standard does not define the behavior, but neither does it make it illegal. – Eric Postpischil Sep 21 '20 at 22:57
  • `c->facility = malloc(200);` ? Where does 200 come from? Is that just some arbitrary number that you hope is large enough? You should do `c->facility = malloc(sizeof *c->facility)`. – William Pursell Sep 21 '20 at 23:07
  • @EricPostpischil The answers under both the previous link and [Why isn't GCC's acceptance of void-pointer arithmetic considered a bug](https://stackoverflow.com/questions/25375240/why-isnt-gccs-acceptance-of-void-pointer-arithmetic-considered-a-bug) tend to consider the behavior as non-conforming, though I am not enough of a C language lawyer to argue that finer point. Regardless, it is certainly not mandated behavior, so the caveat still applies that different compilers may implement it differently, or not at all. – dxiv Sep 21 '20 at 23:09
  • Given `void *c`, you ought to just do `struct car *cp = c`. – William Pursell Sep 21 '20 at 23:10

2 Answers2

0

the assignment asks precisely understand how structure is defined in memory

The memory layout (assuming no padding) would look something like the following.

                -------------------------|
                |          ^             |
    c  ------>  |  year    | sizeof(int) |
                |          v             |
                |------------------------|
                |  type                  |
                |------------------------|
                |  free                  |
                |------------------------|           |--------|            |---|---|---
                |  facility              |  ------>  |  name  |  ------->  | a | b | ..
                |------------------------|           |--------|            |---|---|---
                                                     |  city  |  ---\      
                                                     |--------|     |      |---|---|---
                                                                    \--->  | x | y | ..
                                                                           |---|---|---

To access what would be c->facility->city, for example:

    void *facility = *(void **)( (char *)c + 3 * sizeof(int) );  // skip past year, type, free
    void *city = *(void **)((char *)facility + sizeof(char *));  // skip past name

[ EDIT ]   Without the "no padding" assumption, the code could use the offsetof macro, instead.

    void *facility = *(void **)( (char *)c + offsetof(struct Car, facility) );
dxiv
  • 16,984
  • 2
  • 27
  • 49
  • I don't think that works - it still gives me a segmentation failure: My code prints: `Car: 0x21c0010 Facility: 0x21c0050` when it is created and `Car: 0x21c0010 Facility: 0x21c005000000000` when it is examined in test: https://onlinegdb.com/H1qzEh8SP Which explains the segmentation fault because there is nothing allocated at 0x21c005000000000 – Jerry Jeremiah Sep 21 '20 at 23:15
  • 1
    This is actually same code I have, adding (char *) before facility and sizeof(char*) instead of +8bytes. Better written, but same for compiler. Sadly it does not work. – Ivan Sep 21 '20 at 23:16
  • @Ivan Note the "*assuming no padding*" part. The above will work (using the gcc syntax) if you add `__attribute__((packed))` to `struct Car`, see [here](https://godbolt.org/z/aq1sq7) for example. If you want to keep the default padding, instead, then you must change `sizeof` and replace it with actual [offset](https://stackoverflow.com/questions/7897877/how-does-the-c-offsetof-macro-work) calculations. – dxiv Sep 21 '20 at 23:44
  • Thank you very much dxiv, then the problem I have been fighting was the padding! Thanks! – Ivan Sep 21 '20 at 23:50
  • @Ivan Glad it helped. I edited that part into the answer. – dxiv Sep 21 '20 at 23:56
0

The common way to access a structure when given a void * that points to it is to convert it to the correct type:

void test(void *p)
{
    Car *c = p;

    printf("Year %d\n", c->year);
    printf("Type %d\n", c->type);
    printf("Free %d\n", c->free);
    printf("Facility name %s\n", c->facility->name);
    printf("Facility city %s\n", c->facility->city);
}

Note that I changed the return type of test to void, since you were not returning any value. You should also declare it, with void test(void *);, before calling it.

If you are not allowed to convert the pointer to the proper type, you can calculate the locations of the int members using offsetof, which is defined in <stddef.h>. If necessary, you can also fill in the offsets after discovering them by other means.

However, to access the facility member, we run into problems regarding the C rules, as noted in the comments below. I do not believe there is a fully defined way to do this in strictly conforming C. In that case, this is a bad assignment.

void test(void *p)
{
    char *c = p;

    printf("Year %d\n", * (int *) (c + offsetof(Car, year)));
    printf("Type %d\n", * (int *) (c + offsetof(Car, type)));
    printf("Free %d\n", * (int *) (c + offsetof(Car, free)));

    //  Set f to point to the location of facility within the Car structure.
    char *f = c + offsetof(Car, facility);

    /*  Unfortunately, although we know f points to a pointer to the structure
        containing the name and the city, that structure has no tag, so we
        cannot write a cast to it.  Instead, we use "(struct Car **)" to say f
        points to a pointer to a struct Car.  It does not, but the C standard
        requires that all pointers to structures have the same representation
        and alignment requirements.  This is dubious C code, but I see no
        alternative given the problem constraints.

        Then we dereference that pointer to a structure and convert it to a
        pointer to a char, so we can do address arithmetic.  Again, since we
        have no name for the facility structure, we cannot reference its
        members using offsetof.  Normal C implementations will not add padding
        between members of the same type, so we calculate an offset using the
        size of a "char *" and hope that works.
    */
    f = (char *) (* (struct Car **) f);
    printf("Name %s.\n", * (char **) (f + 0));
    printf("City %s.\n", * (char **) (f + sizeof(char *)));
}
Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • Thanks. Our teacher told us to do not apply the struct to the void * as then is easy to get its contents. – Ivan Sep 22 '20 at 00:08
  • @Ivan: I have provided an alternate method. However, it appears this is a bad assignment; it cannot be done with strictly conforming C, and your teacher should not have assigned it. – Eric Postpischil Sep 22 '20 at 00:27