弹性阵列成员上的结构

时间:2018-06-25 07:21:05

标签: c c99 strict-aliasing

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

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

问题是我需要2个malloc呼叫和2个free。对我来说,函数调用是昂贵的,而malloc是昂贵的。从用户方面来说,简化free消息是非常好的。所以我将定义更改为:

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种消息类型的反序列化的实现...

4 个答案:

答案 0 :(得分:3)

要躲避严格的别名,可以将结构包装在并集内。使用C11,您可以使用匿名结构来摆脱访问“标志”所需的额外级别:

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

现在,您可以执行msg_10* payload = (msg_10*)msg->data;并访问payload,而不必担心严格的别名冲突,因为联合类型包括与对象的有效类型兼容的类型(uint8_t[])。

但是请注意,malloc返回的指针没有有效的类型,除非您通过指向某种类型的指针进行访问。因此,或者,您可以确保在malloc之后使用正确的类型访问数据,并且也不会产生严格的别名冲突。像

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

在不使用dummy的地方,仅是设置有效类型的地方。

答案 1 :(得分:2)

  

我正在编写C程序(g++可编译)

这是一个误会。

C源文件应该由gcc 编译(不是g++编译)。 C ++源文件应由g++编译(而不是gcc)。请记住,GCC的意思是Gnu Compiler Collection(并且在适当配置时还包含gfortrangccgo等。)因此,Fortran源文件应使用gfortran进行编译(如果使用GCC),Go源文件应使用gccgo进行编译(如果使用GCC),Ada代码应进行编译gnat(如果使用GCC),依此类推。

了解有关Invoking GCC的信息。通过还将-v传递到gccg++编译器命令(它应该调用cc1编译器,而不是cc1plus编译器命令)来检查发生了什么。

如果您坚持使用g++(不是gcc)编译C99或C11源文件,这是恕我直言的错误和混乱,请确保至少传递-std=c99(或-std=gnu11等...)和-x c标志。

但是您确实应该修复 build automation或构建过程以使用gcc(而不是g++)来编译C代码。真正的问题就是这一点(Makefile中的某些错误或其他问题)。

在链接时,如果您混合使用C和C ++代码,请使用g++

请注意,flexible array members在C ++中不存在(甚至不存在),即使在将来的C ++ 20中也是如此。在C ++中,您可以使用0作为其声明的大小,例如代码:

#ifdef __cplusplus
#define FLEXIBLE_DIM 0
#else
#define FLEXIBLE_DIM /*empty flexible array member*/
#endif

然后声明:

struct msg {
  int type;
  uint8_t data[FLEXIBLE_DIM]; /* flexible array member */
};

但这只能起作用,因为uint8_tPOD,并且您的g++编译器可能(有时)发出“缓冲区溢出”或“索引超出范围”警告(您应该从不取决于该sizeof字段的编译时间data

答案 2 :(得分:2)

您当然可以使用Makros建立类似的东西; message_header用作所有消息类型的父结构。作为此类结构的第一个成员,它们共享相同的地址。因此,在创建msg(int)并将其强制转换为message_header之后,您可以通过对其调用free来释放它。 (C ++的工作原理相同)

这是您想要的吗?

struct message_header {
    int type;
};

#define msg(T) struct {struct message_header header; T data} 

struct message_header* init_msg_int(int a) {
    msg(int)* result = (msg(int)*)(malloc(sizeof(msg(int))));
    result->data = a;
    return (struct message_header*)result;
}

int get_msg_int(struct message_header* msg) {
    return ((msg(int)*)msg)->data;
}

void free_msg(struct message_header* msg) {
    free(msg);
}    

答案 3 :(得分:0)

没有malloc,没有释放,没有别名,函数调用是内联的,对于简单或未填充的自然对齐结构,,内联函数等效于memcpy或寄存器副本(对于简单的小型结构)结构。对于更复杂的结构,编译器会进行所有繁重的工作。

由于要进行反序列化,缓冲区中的byes的alignemnet可能会被打包并且无法自然对齐。看看packed_struct.h(https://elixir.bootlin.com/linux/v3.8/source/include/linux/unaligned/packed_struct.h)的Linux内核源代码

为每个msg_0..msg_10..msg_(n-1)推出一个函数,而不是u16,u32,u64。从源文件中可以看到,使用一些简单的宏简化每个未对齐类型和内联函数的时机已经成熟。使用您的示例名称

struct msg {
    int type;
};
...
struct msg_10 {
    struct msg MsgStruct; /* or int type; */
    uint32_t flags[4];
    ...
};

#define UNALIGNED_STRUCT_NAME(msg_struct_tag) \
    UNA_##msg_struct_tag

#define DECLARE_UNALIGNED_STRUCT(msg_struct_tag) \
  struct UNALIGNED_STRUCT_NAME(msg_struct_tag) \
  {struct msg_struct_tag x;} __attribute__((__packed__))

#define DESERIALIZE_FN_NAME(msg_struct_tag) \
    deserialize_##msg_struct_tag

#define CALL_DESERIALIZE_STRUCT_FN(msg_struct_tag, pbuf) \
    DESERIALIZE_FN_NAME(msg_struct_tag)(pbuf)

#define DEFINE_DESERIALIZE_STRUCT_FN(msg_struct_tag) \
    static inline \
        struct msg_struct_tag DESERIALIZE_FN_NAME(msg_struct_tag)(const void* p) \
    { \
        const struct UNALIGNED_STRUCT_NAME(msg_struct_tag) *ptr = \
            (const struct UNALIGNED_STRUCT_NAME(msg_struct_tag) *)p; \
        return ptr->x; \
    }

...
DECLARE_UNALIGNED_STRUCT(msg_9);
DECLARE_UNALIGNED_STRUCT(msg_10);
DECLARE_UNALIGNED_STRUCT(msg_11);
...
...
DEFINE_DESERIALIZE_STRUCT_FN(msg_9)
DEFINE_DESERIALIZE_STRUCT_FN(msg_10)
DEFINE_DESERIALIZE_STRUCT_FN(msg_11)
...

反序列化消息10

struct msg_10 ThisMsg = CALL_DESERIALIZE_STRUCT_FN(msg_10, buffer);

或者在缓冲区9的字节9处反序列化消息13

struct msg_13 OtherMsg = CALL_DESERIALIZE_STRUCT_FN(msg_13, &(buffer[9]));