【问题标题】:C Access structure contents from a void pointerC 从 void 指针访问结构内容
【发布时间】:2021-01-08 00:58:34
【问题描述】:

我有一项任务让我浪费了宝贵的时间却一无所获。我必须从作为 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* 直接提取其内容。谢谢。

【问题讨论】:

  • c+4 这已经是非法的了,例如见Pointer arithmetic for void pointer in C
  • 为什么c+4是非法的?您的链接说是非法的,因为它是不完整的类型,但在这种情况下它不是,那么就不是非法的。尤金,不工作。我正在尝试读取存储在 Car 中的指针,然后读取存储在那里的 char *,因为前一个指针而递增 8,然后读取下一个 char * 即城市。感谢您投反对票...
  • @Ivan 这是非法的,因为 c 被声明为 void * 并且指针算术(递增、加法等)对于 void 指针是非法的。想一想,当你有一个int *p; 时,p + 1 指向下一个int。指针的值实际上增加了sizeof int,这是int 占用的字节数。但是当你有一个void *p; 时,你不知道p 指向什么,也不知道something 有多大,所以谈论p + 1 是没有意义的。仅仅因为 gcc 长期以来一直将其作为扩展并不能使其合法 C,从来没有。附言顺便说一句,这不是我的反对意见。
  • @Ivan increase by 4 (int) or 8 (pointer) 我的意思是,c + 4 只是碰巧“工作”,因为您似乎甚至不知道您正在使用的编译器扩展。该代码可能会在其他编译器中给出虚假结果,或者根本无法编译。正确/便携的写法是(char *)c + sizeof(int)
  • @dxiv:添加到void * 是 C 标准定义为 conforming(比 strictly conforming 更大的集合)。 C 标准没有定义行为,但也没有使其非法。

标签: c memory struct structure void-pointers


【解决方案1】:

当给定一个指向它的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 的返回类型更改为void,因为您没有返回任何值。您还应该在调用它之前使用void test(void *); 声明它。

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

但是,要访问facility 成员,我们会遇到有关 C 规则的问题,如下面的 cmets 中所述。我不相信在严格符合 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 *)));
}

【讨论】:

  • 谢谢。我们的老师告诉我们不要将结构应用于 void *,因为这样很容易得到它的内容。
  • @Ivan:我提供了另一种方法。然而,这似乎是一个糟糕的任务;它不能用严格符合的 C 来完成,而且你的老师不应该分配它。
【解决方案2】:

作业要求准确理解内存中的结构是如何定义的

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

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

访问c-&gt;facility-&gt;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) );

【讨论】:

  • 我认为这不起作用 - 它仍然给我一个分段失败:我的代码在创建时打印:Car: 0x21c0010 Facility: 0x21c0050,在测试中检查时打印Car: 0x21c0010 Facility: 0x21c005000000000onlinegdb.com/H1qzEh8SP解释了分段错误,因为在 0x21c005000000000 处没有分配任何内容
  • 这实际上与我的代码相同,在设施和 sizeof(char) 之前添加 (char ) 而不是 +8bytes。写得更好,但编译器也一样。遗憾的是它不起作用。
  • @Ivan 请注意“假设没有填充”部分。如果您将__attribute__((packed)) 添加到struct Car,以上将起作用(使用gcc 语法),例如参见here。如果要保留默认填充,则必须更改 sizeof 并将其替换为实际的 offset 计算。
  • 非常感谢dxiv,那么我一直在努力解决的问题是填充!谢谢!
  • @Ivan 很高兴它有帮助。我将那部分编辑为答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-12-05
  • 2012-02-03
  • 2014-05-14
  • 2019-01-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多