【问题标题】:can I use struct member from another struct?我可以使用另一个结构中的结构成员吗?
【发布时间】:2020-11-25 15:26:13
【问题描述】:

从这些问题:Casting one struct pointer to another - C,我想知道,是否可以使用类型为“特定”结构的“一般”结构的成员:

#include <stdio.h>
#include <stdlib.h>

enum type_e { CONS, ATOM, FUNC, LAMBDA };

typedef struct {
    enum type_e type;
} object;

typedef struct {
    enum type_e type;
    char *expression;
} lambda_object;

typedef struct {
    enum type_e type;
    object *car, *bus;
    int value;
} cons_object;


object *traverse(object *o){
    if (o->type == CONS){
        cons_object *cons = (cons_object*)o;
        traverse(cons->car);
        traverse(cons->bus);
        return (object*)cons;
    } else if (o->type == LAMBDA) {
        lambda_object *lam = (lambda_object*)o;
        return (object*)lam;    
    }
    return 0;
}

int main(){
    lambda_object l = {LAMBDA, "value to print\n"};
    object *p = traverse((object*)&l);
    printf("sizeof(object):%lu\nsizeof(lambda_object):%lu\n",sizeof(object), sizeof(lambda_object));
    printf("%s\n",*(p+4));

}

没有错误,只是command terminated,所以我不知道出了什么问题,但怀疑我试图推迟错误的地址*(p+4),但我知道,有一个指向我的字符串的指针。从lambda_object的定义,在enum(4字节长,和int一样)之后,有我的指针。所以我不应该取消引用错误的地址,但我仍然不能。为什么?

输出

a.c: In function ‘main’:
a.c:46:11: warning: format ‘%s’ expects argument of type ‘char *’, but argument 2 has type ‘object’ {aka ‘struct <anonymous>’} [-Wformat=]
  printf("%s\n",*(p+4));
          ~^    ~~~~~~

Press ENTER or type command to continue
sizeof(object):4
sizeof(lambda_object):16

Command terminated

编辑: 我试过(char*)p[4],还是终止

【问题讨论】:

  • 这就是union 的用途。
  • 指针运算不是这样工作的。 p+4p 之后指向struct object 类型的4 个元素。这与p[4] 完全相同。
  • 如果 sizeof(object) 不是 1(可能是这种情况,因为它包含一个可能存储为 int 的枚举),那么 (p+4) 是一个距离 p 4 个字节的指针。如果这就是您想要的,请使用((char)p + 4)。但它是丑陋的编程,容易出错。更好的是使用union 来定义占用相同空间的结构。
  • @milanHrabos 和 p[4] 你犯了和以前一样的错误! [] 比强制绑定更紧密(具有更高的优先级)。是什么阻止您使用 ((lambda_object*)p)-&gt;expression 之类的东西?
  • 我不清楚你想要达到什么目的。也许如果您描述有人可以提出更好的方法。在这里,您正在玩​​火并做出不成立的假设。无论如何 - 只是为了好玩 - 试试:printf("%s\n", *(char**)((char*)p + ((unsigned long long)&amp;l.expression - (unsigned long long)&amp;l)));

标签: c struct casting type-conversion


【解决方案1】:

首先,就像许多其他人在 cmets 中指出的那样,这不是实现您想要实现的任何目标的理想方式。最简单、最便携的方法是使用 ((lambda_object*)p)-&gt;expression 之类的东西。

至于你的代码为什么会这样,或许我可以解释一下。

在此之前,这是您的程序,“固定”以完全按照您想要的方式打印存储的字符串。

#include <stdio.h>
#include <stdlib.h>

enum type_e { CONS, ATOM, FUNC, LAMBDA };

typedef struct {
    enum type_e type;
} object;

typedef struct {
    enum type_e type;
    char *expression;
} lambda_object;

typedef struct {
    enum type_e type;
    object *car, *bus;
    int value;
} cons_object;


object *traverse(object *o){
    if (o->type == CONS){
        cons_object *cons = (cons_object*)o;
        traverse(cons->car);
        traverse(cons->bus);
        return (object*)cons;
    } else if (o->type == LAMBDA) {
        lambda_object *lam = (lambda_object*)o;
        return (object*)lam;    
    }
    return 0;
}

int main(){
    lambda_object l = {LAMBDA, "value to print\n"};
    object *p = traverse((object*)&l);
    printf("sizeof(object):%lu\nsizeof(lambda_object):%lu\n",sizeof(object), sizeof(lambda_object));

    printf("%s\n",*((char**)((char*)p+8))); // Note the weird typecasts and p + 8 instead of 4
}

回到这个原因,假设一台 64 位机器,你的 lambda_object 结构在内存中看起来像这样:

| Bytes 0 to 3 | Bytes 4 to 7 | Bytes 8 to 16                |
--------------------------------------------------------------
| type         | padding      | expression                   |
--------------------------------------------------------------

这里应该注意的是expression 是指向字符串的指针,而不是字符串本身。因此,即使 type 仅 4 字节长,expression 也仅从 p + 8 开始,而不是像人们预期的那样以 p + 4 开始。从 4 到 7 的字节将简单地留空作为填充。这是因为 64 位指针必须从 64 位对齐的地址开始。

但是((char *)p + 8) 应该可以正常工作吗?不幸的是没有!我们从p 开始作为指向lambda_object 的指针。我们已将 p 类型转换为 char 指针以到达此结构中的正确偏移量,但这意味着您告诉编译器在位置 p + 8 处有一个字符,而实际上有一个 指针 指向一个字符。如果你将它传递给printf(),它会尝试将此指针打印为字符串,从而导致乱码。

您现在应该做的是取消引用指针p + 8 以获取指针expression,方法是告诉编译器将p + 8 视为指向指针的指针。这是通过对(char**) 的类型转换来实现的。现在您可以取消引用它一次以获得一个 char 指针,最后将它传递给 printf()

【讨论】:

  • 虽然+ 8 对于大多数(如果不是全部)64 位系统可能是正确的,但在 32 位系统上可能是错误的。唯一的方法是计算expression 的偏移量,而不是使用硬编码值。为此,请参阅问题下的 cmets
  • @4386427 同意两个 cmets!我的想法是解释 OP 的期望和他得到的行为之间的差异。我想我们所有人都一致同意,最好的方法是简单地使用结构来正确计算偏移量。
  • @th33lf 我只是无法理解,为什么要在立即取消引用双指针时使用它。为什么不直接使用char* 指针并将其地址调整到字符串的开头。然后我意识到,“字符串指针”(char*)text 段中(或只读),所以它是固定的,我唯一的选择(使用算术)是使用另一个指针来查找那个固定字符指针(因此是双重指针,然后取消引用它)。我唯一不明白的是,为什么我需要使用 2 个指针而不是一个,segment 是答案
  • @milanHrabos 不。我们需要双指针的原因是,如果您取消引用 char*,您会得到一个 char,它是一个字节。当您取消引用它时,只会从内存中读取一个字节。但是你需要传递给printf 的是一个8 字节的指针,你只能通过取消引用char** 来获得它。您也可以在此处将类型转换为unsigned long*(单指针)而不是char**,它仍然可以工作,尽管编译器会抱怨。
  • @th33lf 那么,为什么还要取消引用呢?我可以计算 (char*) 地址,不需要取消引用,因此不需要双指针
猜你喜欢
  • 1970-01-01
  • 2021-11-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多