下面的逻辑工作得很好,但我不确定标准所说的警告,以及将结构体转换为uint8_t *
或char *
以发送到消息队列(其本身也接受指向缓冲区的指针)或甚至函数是否完全安全?
我的理解是只要uint8_t
被认为是一个字节(char
是),它可以用来寻址任何一组字节
typedef struct
{
uint8_t a;
uint8_t b;
uint16_t c;
} } __attribute__((packed)) Pkt;
int main()
{
Pkt pkt = {.a = 4, .b = 12, .c = 300};
mq_send(mq, (char *) &pkt, sizeof(pkt), 0);
}
也许它类似于传递一个强制转换指针给一个函数(在接收端),它根据字节解析数据
typedef struct
{
uint8_t a;
uint8_t b;
uint16_t c;
} __attribute__((packed)) Pkt;
void foo(uint8_t *ptr)
{
uint8_t a = *ptr++;
uint8_t b = *ptr++;
uint16_t str = (*(ptr+1) << 8) | *ptr;
printf ("A: %d, B: %d, C: %dn", a, b, str);
}
int main()
{
Pkt pkt = {.a = 4, .b = 12, .c = 300};
foo((uint8_t *) &pkt);
}
C故意允许访问对象的字节,并通过传输表示对象的字节并从传输的字节重建对象来支持对象通信。然而,它应该正确地完成,并且有一些问题需要处理。
应该使用字符类型
首选的类型是unsigned char
。这是首选的两个原因:
- C标准定义了使用字符类型访问对象表示的行为。字符类型为:
char
、signed char
、unsigned char
。该标准不要求uint8_t
是字符类型。虽然它可能具有与unsigned char
相同的大小和一般属性,但它可能是一个扩展的整数类型,而不是unsigned char
(或char
)的别名。在这种情况下,C标准没有使用uint8_t
定义访问对象字节的行为。 unsigned char
优先于char
或signed char
,以避免在各种C操作中出现有符号整数的问题。
发送方和接收方必须就对象的表示或用于发送它们的协议达成一致。
如果发送方和接收方使用相同的C实现对被传输的对象使用相同的定义(例如相同的结构定义)进行编译,他们将在表示上达成一致。但是,在不同的C实现之间,有必要确保在传输的字节如何表示对象方面有明确的协议。如您的代码所示,结构是打包的,这应该可以解决结构内部可能存在填充的问题。其他注意事项包括:
- 整数的字节顺序。小端优先(字节从最不重要到最重要的顺序)和大端优先(相反)是常见的,尽管其他情况也是可能的。大端序在网络协议中最为常见。
- 非整数的表示,如浮点格式。IEEE-754浮点标准规定了一些被广泛使用的交换格式。
- 结构的布局是相同的,包括成员的类型。
- 理论上,字节内的位的顺序必须是一致的,但是如果网络服务在字节级别上运行,这就不是问题了。
请注意,当然,由于需要运行程序中的上下文(如指针和文件句柄),有些对象本质上是不可能通过字节表示发送的。
额外注意
另一个要防止的危险是将字节缓冲区解释为另一个对象。C标准定义了使用字符类型访问对象(例如,定义为结构的东西)的字节的行为,但它没有定义相反的行为。有时naïve程序员会创建字符类型的数组,读入网络消息,然后将数组指针转换为指向结构类型的指针。这与两个问题相冲突:- 如果对齐不正确,则没有定义转换。(对于打包数组,这应该不是问题,我们希望有一个字节的对齐要求。)
- C标准没有定义将字符数组访问为不同的、不兼容的类型。
将接收到的字节重新组装成对象的正确方法是将它们复制到声明为所需类型的内存中,或者复制到分配的内存中(如malloc
),以便将其解释为预期对象。这可以通过将字节从缓冲区复制到目标内存或直接将目标内存传递给网络读例程来完成,以便它直接填充字节。