【问题标题】:How to cast a struct onto a different struct member如何将结构强制转换为不同的结构成员
【发布时间】:2017-10-31 12:39:42
【问题描述】:

如何让变量指向不同结构的成员?这是我正在尝试做的,但是第三行失败了。

volatile uint8_t tx_message_buffer[sizeof(MESSAGE)];
struct MESSAGE *tx_message = (MESSAGE *)tx_message_buffer;
struct PAYLOAD *tx_payload = (PAYLOAD *)tx_message->payload;

这是结构定义。

#define MSG_MAX_PAYLOAD_LENGTH  64

typedef struct PAYLOAD {
    uint8_t descriptor;
    uint8_t parameters[MSG_MAX_PAYLOAD_LENGTH-1];
};

typedef struct MESSAGE {
    uint8_t address;
    uint8_t length;
    PAYLOAD payload;
    uint8_t checksum;
};

【问题讨论】:

  • 旁注:这不是序列化的好方法。您很容易遇到对齐和严格的混叠违规问题。这段代码不会发生这种情况,因为所有类型都是uint8_t,但是使用任何其他类型,你就会遇到问题。 (而且我还没有提到填充和字节序问题)。
  • @user694733 你能详细说明一下吗?我知道我可能会遇到问题,例如使用数组,但还有哪些其他类型会导致问题?您对如何将所有消息元素收集到一种类型有更好的想法吗?
  • 基本上,您不能使用指向一种类型的转换指针来访问 C 中真正属于其他类型的数据。在某些情况下,这些规则有例外,但它确实是一个雷区。您可能(并且将会)被编译器做出不成立的假设或意外生成在您的硬件上无法可靠运行的代码所困扰。正确的方法(最便携和可靠)是通过使用位移和掩码并将结果分配给字节数组,手动将每个结构成员转换为字节。
  • @user694733 我能麻烦你写一个简单的例子吗?不确定你的意思的语法。
  • 虽然不完美,this answer(以及关于该问题的其他答案)对这个想法有一个简短的概述。

标签: c casting


【解决方案1】:

这段代码有很多问题。

  • 正如其他答案中所指出的,您不能将指针设置为指向 PAYLOAD payload; 成员,您需要指向其地址 &tx_message->payload

  • typedef struct PAYLOAD {} 应该是typedef struct {} PAYLOAD

  • (MESSAGE *)tx_message_buffer 是一个完全狂野的演员阵容,它调用了几个定义不明确的行为案例。首先,你永远不应该抛弃volatile 限定符。而且,一旦您取消引用此结构,您将违反strict aliasing 并调用未定义的行为。任何事情都有可能发生。

    要解决这些指针错误,您可以执行类似的操作:

    typedef struct {
        uint8_t address;
        uint8_t length;
        PAYLOAD payload;
        uint8_t checksum;
    } MESSAGE;
    
    typedef union {
      MESSAGE message;
      uint8_t tx_message_buffer[sizeof(MESSAGE)];
    } message_something;
    

    此代码有效且定义明确。

  • 使用结构来表示数据协议是不好的做法,因为您必须确保结构根本不包含任何填充。 MESSAGE 结构中的内存布局绝不保证与数据协议的内存布局相对应。该结构可能具有填充字节以适应特定 CPU 的对齐要求。

    根据您的可移植性要求,使用#pragma pack(1) 等非标准 C 禁用填充可能足够,也可能不够。要实现完全的可移植性,您可能必须编写序列化/反序列化例程。

【讨论】:

    【解决方案2】:

    您的代码中有一个更大的问题:第二行的强制转换无效,因为struct MESSAGE 的存储空间通常可能与char[] 数组有不同的对齐要求。例如,将descriptor 的类型更改为uint32_t 可能会在某些平台上强制为整个结构使用偶数地址。

    反之亦然,因为您可以将任何对象指针转换为char *

    volatile struct MESSAGE tx_message;
    volatile uint8_t *tx_message_buffer = (char*)tx_message;
    

    第三行失败是因为你没有取PAYLOAD struct的指针:

    struct PAYLOAD tx_payload = &tx_message.payload;
    

    不需要转换结果,因为tx_message.payload 已经是正确的类型。

    【讨论】:

    • 为什么是括号?
    • @Elazar 我发现地址表达式在非平凡参数周围加上括号更易读。显然,-> 具有更高的优先级,因此可以去掉括号。
    • 收到错误消息:“取消引用指向不完整类型的指针。”
    • @Oystein 但是声明变得无效,因为地址表达式确实不是常量,因此它不适合静态上下文中的初始化程序。
    • @Oystein 当然,如果你的程序有一个init方法,你绝对可以用它来设置tx_payload指针。
    【解决方案3】:

    通过使用,

    PAYLOAD payload;
    

    你得到的是一个变量而不是一个指针。意思是

    message->payload;
    

    不是指针。

    你需要使用指针。

    PAYLOAD * payload;
    

    或者获取结构体的地址

    &message->payload;
    

    【讨论】:

      猜你喜欢
      • 2019-08-25
      • 1970-01-01
      • 1970-01-01
      • 2017-07-25
      • 1970-01-01
      • 1970-01-01
      • 2021-09-07
      • 1970-01-01
      • 2016-09-24
      相关资源
      最近更新 更多