首页 > 解决方案 > 灵活数组成员上的结构

问题描述

我正在编写一个 C 程序(g++ 可编译),它必须处理许多不同的结构,所有这些都来自具有预定义格式的缓冲区。格式指定我应该加载哪种类型的结构。这可以使用联合来解决,但是结构大小的巨大差异使我决定使用一个带有 void * 的结构:

struct msg {
    int type;
    void * data; /* may be any of the 50 defined structures: @see type */
};

问题是我需要 2 个malloc电话和 2个电话free。对我来说,函数调用很昂贵而且malloc很昂贵。free从用户方面来说,简单的 msgs会很棒。所以我将定义更改为:

struct msg {
    int type;
    uint8_t data[]; /* flexible array member */
};
...
struct msg_10 {
    uint32_t flags[4];
    ...
};

每当我需要反序列化消息时,我都会:

struct msg * deserialize_10(uint8_t * buffer, size_t length) {
    struct msg * msg = (struct msg *) malloc(offsetof(struct msg, data) + sizeof(struct msg_10));
    struct msg_10 * payload = (__typeof__(payload))msg->data;

    /* load payload */
    return msg;
}

并获得该结构的成员:

uint32_t msg10_flags(const struct msg * msg, int i)
{
    return ((struct msg_10 *)(msg->data))->flags[i];
}

有了这个改变,gcc(和 g++)发出了一个很好的warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]消息。

我认为这是关于如何以某种有效的方式在 C 中表示一系列消息的常见问题(但我在这里没有找到答案)。

我理解为什么会出现警告,我的问题如下:

  1. 是否可以在没有警告的情况下实施这样的事情,或者它是否存在内在缺陷?(或不是唯一的:P,我几乎确信我应该重构)
  2. 使用以下代码表示消息会更好吗?

    struct msg {
        int type;
    };
    ...
    struct msg_10 {
        struct msg; /* or int type; */
        uint32_t flags[4];
        ...
    };
    
  3. 如果是,注意事项?我可以始终编写和使用以下内容吗?

    struct msg * deserialize_10(uint8_t * buffer, size_t length) {
        struct msg_10 * msg = (struct msg_10 *) malloc(sizeof(struct msg_10));
    
        /* load payload */
        return (struct msg *)msg;
    }
    
    uint32_t msg10_flags(const struct msg * msg, int i) {
        const struct msg_10 * msg10 = (const struct msg_10 *) msg;
        return msg10->flags[i];
    }
    
  4. 任何其他?

我忘了说这在低级系统上运行,性能是优先考虑的,但总而言之,真正的问题是如何处理这种“多消息”结构。我可能会重构一次,但要更改 50 种消息类型的反序列化的实现......

标签: cc99strict-aliasing

解决方案


为了避开严格的别名,你可以将你的结构包装在一个联合中。使用 C11,您可以使用匿名结构来摆脱访问“标志”所需的额外级别:

typedef union
{
  struct
  {
    uint32_t flags[4];
  };  
  uint8_t bytes[ sizeof(uint32_t[4]) ];
} msg_10;

现在您可以在不担心严格的别名违规的情况下进行msg_10* payload = (msg_10*)msg->data;访问,因为联合类型包含与对象的有效类型兼容的类型 ( )。payloaduint8_t[]

但是请注意,在malloc您通过指向特定类型的指针访问它之前,返回的指针没有有效类型。因此,或者,您可以确保在 malloc 之后访问具有正确类型的数据,这也不会产生严格的别名冲突。就像是

struct msg_10 * msg = malloc(sizeof(struct msg_10));
struct msg_10 dummy = *msg; 

dummy不会用到的地方,就是用来设置有效类型的。


推荐阅读