我有这样的结构:
typedef struct _HEADER_IO
{
uint8_t field1 : 2;
uint8_t field2 : 4;
uint8_t field3 : 1;
uint8_t field4 : 1;
uint16_t field5;
uint8_t field6;
} HEADER_IO;
它基本上是一个将通过tcp发送的消息头。服务器读取它,以便它知道缓冲区中的数据。但是由于某种原因,大小为4字节(2 + 4 + 1 + 1第一字节+字段5 + 1字节字段6的2字节)的大小为6字节。
在内存视图中查找它是:
XX AA XX XX XX AA
而不是:
XX XX XX XX
无论我做什么,AA都不会被设定。这是一个问题,因为我计划将标头设置为send()
到服务器,并包含额外的字节,使服务器解释标头错误。我做错了什么?
答案 0 :(得分:1)
一般来说,将bitfields用于这类事情是个坏主意。由于您无法事先知道这些位最终会在哪个字节中出现,并且因为存在填充和对齐问题。
在我看来,最好“拥有”这样一个事实:你需要更多地控制外部表示而不是C结构给你的,并且手动完成。您当然可以将结构保留为内存(内部)表示。
基本上,你会写一个像:
这样的函数size_t header_serialize(unsigned char *buf, size_t max, const HEADER_IO *header);
在buf
的内存中,它的作用是构建代表header
的正确字节序列。
澄清(基于评论),目的是阅读header
中的字段,而不仅仅是这样做。
memcpy(buf, header, sizeof *header); /* DON'T DO THIS! */
相反,您应该从header
的字段逐个字节地组装预期的外部表示。这样,无论编译器对header
的内存格式做什么,您始终都会获得相同的外部表示。
答案 1 :(得分:0)
在标准C中,您无法理解struct成员可以在它们之间插入填充。您必须编写一个函数来解码数据并在处理之前将其存储在结构中。这是因为在某些体系结构中,未对齐的内存访问(从未对齐的指针读取,例如4个字节)非常昂贵,C将自动填充结构以避免成本。没有标准方法可以打开或关闭此功能。
例如,在GCC中,您可以在结构定义之后添加__attribute__((packed))
,并且Visual Studio具有一些GCC也支持的#pragma
命令(请参阅http://gcc.gnu.org/onlinedocs/gcc/Structure_002dPacking-Pragmas.html),但要注意总体来说这是非标准。
由于你的评论提到它是一个Windows程序,如果你在结构定义之前添加它可能会有效:
#pragma pack(push,1)
这之后:
#pragma pack(pop)
虽然编写代码以更加手动解码标题会更方便,但上述方法应该更快。