C 从空指针访问结构内容

C Access structure contents from a void pointer

我有一项任务让我失去了宝贵的时间而没有成功。我必须从作为 void *c 传递给函数的结构中读取内容。除了指向另一个结构的字段外,我可以毫无问题地读取它的内容。示例代码:

#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);
}

我看不懂的部分是设施名称和设施城市。我知道我可以使用 -> 轻松访问,但作业要求准确理解结构在内存中的定义方式,并使用 void* 直接提取其内容。谢谢

the assignment asks precisely understand how structure is defined in memory

内存布局(假设没有填充)如下所示。

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

要访问 c->facility->city,例如:

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

[ EDIT ] 如果没有“无填充”假设,代码可以使用 offsetof 宏。

    void *facility = *(void **)( (char *)c + offsetof(struct Car, facility) );

当给定一个指向结构的 void * 时,访问结构的常用方法是将其转换为正确的类型:

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);
}

请注意,我将 test 的 return 类型更改为 void,因为您没有 returning 任何值。您还应该在调用它之前用 void test(void *); 声明它。

如果不允许将指针转换为正确的类型,则可以使用 <stddef.h> 中定义的 offsetof 计算 int 成员的位置。如有需要,您也可以通过其他方式发现偏移量后再进行填写。

但是,要访问 facility 成员,我们 运行 遇到了有关 C 规则的问题,如下面的评论所述。我不相信在严格符合 C 的情况下有完全定义的方法来做到这一点。在那种情况下,这是一个错误的分配。

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 *)));
}