使用 GCC 结构位打包的字节顺序
byte order using GCC struct bit packing
我正在使用 GCC 结构位字段尝试解释 8 字节 CAN 消息数据。我写了一个小程序作为一种可能的消息布局的例子。代码和注释应该描述我的问题。我分配了 8 个字节,以便所有 5 个信号都应等于 1。正如 Intel PC 上的输出所示,情况并非如此。我处理的所有 CAN 数据都是 big endian,而且它们几乎从不打包成 8 位对齐的事实使得 htonl() 和朋友在这种情况下毫无用处。有人知道解决方案吗?
#include <stdio.h>
#include <netinet/in.h>
typedef union
{
unsigned char data[8];
struct {
unsigned int signal1 : 32;
unsigned int signal2 : 6;
unsigned int signal3 : 16;
unsigned int signal4 : 8;
unsigned int signal5 : 2;
} __attribute__((__packed__));
} _message1;
int main()
{
_message1 message1;
unsigned char incoming_data[8]; //This is how this message would come in from a CAN bus for all signals == 1
incoming_data[0] = 0x00;
incoming_data[1] = 0x00;
incoming_data[2] = 0x00;
incoming_data[3] = 0x01; //bit 1 of signal 1
incoming_data[4] = 0x04; //bit 1 of signal 2
incoming_data[5] = 0x00;
incoming_data[6] = 0x04; //bit 1 of signal 3
incoming_data[7] = 0x05; //bit 1 of signal 4 and signal 5
for(int i = 0; i < 8; ++i){
message1.data[i] = incoming_data[i];
}
printf("signal1 = %x\n", message1.signal1);
printf("signal2 = %x\n", message1.signal2);
printf("signal3 = %x\n", message1.signal3);
printf("signal4 = %x\n", message1.signal4);
printf("signal5 = %x\n", message1.signal5);
}
由于编译器和体系结构之间的结构打包顺序不同,因此最好的选择是使用辅助函数 pack/unpack 二进制数据。
例如:
static inline void message1_unpack(uint32_t *fields,
const unsigned char *buffer)
{
const uint64_t data = (((uint64_t)buffer[0]) << 56)
| (((uint64_t)buffer[1]) << 48)
| (((uint64_t)buffer[2]) << 40)
| (((uint64_t)buffer[3]) << 32)
| (((uint64_t)buffer[4]) << 24)
| (((uint64_t)buffer[5]) << 16)
| (((uint64_t)buffer[6]) << 8)
| ((uint64_t)buffer[7]);
fields[0] = data >> 32; /* Bits 32..63 */
fields[1] = (data >> 26) & 0x3F; /* Bits 26..31 */
fields[2] = (data >> 10) & 0xFFFF; /* Bits 10..25 */
fields[3] = (data >> 2) & 0xFF; /* Bits 2..9 */
fields[4] = data & 0x03; /* Bits 0..1 */
}
请注意,由于连续的字节被解释为单个无符号整数(以大端字节顺序),因此以上内容将是完全可移植的。
当然,您可以使用结构来代替字段数组;但它根本不需要与在线结构有任何相似之处。但是,如果您有多个不同的结构要解包,那么(最大宽度)字段数组通常会变得更容易和更健壮。
所有理智的编译器都会很好地优化上面的代码。特别是,带有 -O2
的 GCC 做得非常好。
相反,将这些相同的字段打包到缓冲区,非常相似:
static inline void message1_pack(unsigned char *buffer,
const uint32_t *fields)
{
const uint64_t data = (((uint64_t)(fields[0] )) << 32)
| (((uint64_t)(fields[1] & 0x3F )) << 26)
| (((uint64_t)(fields[2] & 0xFFFF )) << 10)
| (((uint64_t)(fields[3] & 0xFF )) << 2)
| ( (uint64_t)(fields[4] & 0x03 ) );
buffer[0] = data >> 56;
buffer[1] = data >> 48;
buffer[2] = data >> 40;
buffer[3] = data >> 32;
buffer[4] = data >> 24;
buffer[5] = data >> 16;
buffer[6] = data >> 8;
buffer[7] = data;
}
请注意,掩码定义了字段长度(0x03
= 0b11(2 位),0x3F
= 0b111111(16 位),0xFF
= 0b11111111(8 位), 0xFFFF
= 0b1111111111111111(16 位));并且移位量取决于每个字段中最低有效位的位位置。
为了验证这些功能是否有效,打包、解包、重新打包和重新打包一个缓冲区,该缓冲区应包含除其中一个字段外全为零的缓冲区,并验证数据在两次往返中保持正确。它通常足以检测典型的错误(错误的位移量、掩码中的拼写错误)。
请注意,文档将是确保代码保持可维护性的关键。我会亲自在上面的每个功能之前添加注释块,类似于
/* message1_unpack(): Unpack 8-byte message to 5 fields:
field[0]: Foobar. Bits 32..63.
field[1]: Buzz. Bits 26..31.
field[2]: Wahwah. Bits 10..25.
field[3]: Cheez. Bits 2..9.
field[4]: Blop. Bits 0..1.
*/
字段 "names" 反映了他们在文档中的名字。
我正在使用 GCC 结构位字段尝试解释 8 字节 CAN 消息数据。我写了一个小程序作为一种可能的消息布局的例子。代码和注释应该描述我的问题。我分配了 8 个字节,以便所有 5 个信号都应等于 1。正如 Intel PC 上的输出所示,情况并非如此。我处理的所有 CAN 数据都是 big endian,而且它们几乎从不打包成 8 位对齐的事实使得 htonl() 和朋友在这种情况下毫无用处。有人知道解决方案吗?
#include <stdio.h>
#include <netinet/in.h>
typedef union
{
unsigned char data[8];
struct {
unsigned int signal1 : 32;
unsigned int signal2 : 6;
unsigned int signal3 : 16;
unsigned int signal4 : 8;
unsigned int signal5 : 2;
} __attribute__((__packed__));
} _message1;
int main()
{
_message1 message1;
unsigned char incoming_data[8]; //This is how this message would come in from a CAN bus for all signals == 1
incoming_data[0] = 0x00;
incoming_data[1] = 0x00;
incoming_data[2] = 0x00;
incoming_data[3] = 0x01; //bit 1 of signal 1
incoming_data[4] = 0x04; //bit 1 of signal 2
incoming_data[5] = 0x00;
incoming_data[6] = 0x04; //bit 1 of signal 3
incoming_data[7] = 0x05; //bit 1 of signal 4 and signal 5
for(int i = 0; i < 8; ++i){
message1.data[i] = incoming_data[i];
}
printf("signal1 = %x\n", message1.signal1);
printf("signal2 = %x\n", message1.signal2);
printf("signal3 = %x\n", message1.signal3);
printf("signal4 = %x\n", message1.signal4);
printf("signal5 = %x\n", message1.signal5);
}
由于编译器和体系结构之间的结构打包顺序不同,因此最好的选择是使用辅助函数 pack/unpack 二进制数据。
例如:
static inline void message1_unpack(uint32_t *fields,
const unsigned char *buffer)
{
const uint64_t data = (((uint64_t)buffer[0]) << 56)
| (((uint64_t)buffer[1]) << 48)
| (((uint64_t)buffer[2]) << 40)
| (((uint64_t)buffer[3]) << 32)
| (((uint64_t)buffer[4]) << 24)
| (((uint64_t)buffer[5]) << 16)
| (((uint64_t)buffer[6]) << 8)
| ((uint64_t)buffer[7]);
fields[0] = data >> 32; /* Bits 32..63 */
fields[1] = (data >> 26) & 0x3F; /* Bits 26..31 */
fields[2] = (data >> 10) & 0xFFFF; /* Bits 10..25 */
fields[3] = (data >> 2) & 0xFF; /* Bits 2..9 */
fields[4] = data & 0x03; /* Bits 0..1 */
}
请注意,由于连续的字节被解释为单个无符号整数(以大端字节顺序),因此以上内容将是完全可移植的。
当然,您可以使用结构来代替字段数组;但它根本不需要与在线结构有任何相似之处。但是,如果您有多个不同的结构要解包,那么(最大宽度)字段数组通常会变得更容易和更健壮。
所有理智的编译器都会很好地优化上面的代码。特别是,带有 -O2
的 GCC 做得非常好。
相反,将这些相同的字段打包到缓冲区,非常相似:
static inline void message1_pack(unsigned char *buffer,
const uint32_t *fields)
{
const uint64_t data = (((uint64_t)(fields[0] )) << 32)
| (((uint64_t)(fields[1] & 0x3F )) << 26)
| (((uint64_t)(fields[2] & 0xFFFF )) << 10)
| (((uint64_t)(fields[3] & 0xFF )) << 2)
| ( (uint64_t)(fields[4] & 0x03 ) );
buffer[0] = data >> 56;
buffer[1] = data >> 48;
buffer[2] = data >> 40;
buffer[3] = data >> 32;
buffer[4] = data >> 24;
buffer[5] = data >> 16;
buffer[6] = data >> 8;
buffer[7] = data;
}
请注意,掩码定义了字段长度(0x03
= 0b11(2 位),0x3F
= 0b111111(16 位),0xFF
= 0b11111111(8 位), 0xFFFF
= 0b1111111111111111(16 位));并且移位量取决于每个字段中最低有效位的位位置。
为了验证这些功能是否有效,打包、解包、重新打包和重新打包一个缓冲区,该缓冲区应包含除其中一个字段外全为零的缓冲区,并验证数据在两次往返中保持正确。它通常足以检测典型的错误(错误的位移量、掩码中的拼写错误)。
请注意,文档将是确保代码保持可维护性的关键。我会亲自在上面的每个功能之前添加注释块,类似于
/* message1_unpack(): Unpack 8-byte message to 5 fields:
field[0]: Foobar. Bits 32..63.
field[1]: Buzz. Bits 26..31.
field[2]: Wahwah. Bits 10..25.
field[3]: Cheez. Bits 2..9.
field[4]: Blop. Bits 0..1.
*/
字段 "names" 反映了他们在文档中的名字。