函数反转字节顺序的问题

Issue with a function reversing byte order

我正在研究 CAN 帧,为了解码它们的有效负载,我必须颠倒它们的顺序。 有效载荷是在 can 帧中读取的 uint8_t payload[8](有效载荷中的 8 个字节),具有正常工作的外部函数。 我有这个功能:

static inline uint64_t reverse_byte_order(uint64_t x)
{
    x = (x & 0x00000000FFFFFFFF) << 32 | (x & 0xFFFFFFFF00000000) >> 32;
    x = (x & 0x0000FFFF0000FFFF) << 16 | (x & 0xFFFF0000FFFF0000) >> 16;
    x = (x & 0x00FF00FF00FF00FF) << 8  | (x & 0xFF00FF00FF00FF00) >> 8;
    return x;
}

所以基本上我只是调用 reverse_byte_order(*(uint64_t*)payload),但是经过一些测试,我们无法在解码后获得正确的消息,并意识到问题出在这个函数上。

一个不起作用的测试是将 payload[0]=0x40 和其他所有内容都放入 0x00。输出为 0,而它应该是 0x 40 00 00 00 00 00 00 00。

任何帮助将不胜感激。

编辑:这是一个 MCVE,如果您想进行一些测试,您可以将其复制粘贴到您的 IDE 中:

#include <stdint.h>

static inline uint64_t reverse_byte_order(uint64_t x)
{
    printf("\nInitial x : %016x", x);
    x = (x & 0x00000000FFFFFFFF) << 32 | (x & 0xFFFFFFFF00000000) >> 32;
    printf("\nFirst x : %016x", x); //in our example here it doesn't work anymore as x is 0
    x = (x & 0x0000FFFF0000FFFF) << 16 | (x & 0xFFFF0000FFFF0000) >> 16;
    printf("\nSecond x : %016x", x);
    x = (x & 0x00FF00FF00FF00FF) << 8  | (x & 0xFF00FF00FF00FF00) >> 8;
    printf("\nFinal x : %016x", x);
    return x;
}

int main(int argc, char const *argv[])
{
    uint8_t data[8];
    data[0]=0x11;
    data[1]=0x00;
    data[2]=0x00;
    data[3]=0x00;
    data[4]=0x00;
    data[5]=0x00;
    data[6]=0x00;
    data[7]=0x00;


    uint64_t indata = reverse_byte_order(*(uint64_t*)data);
}

如果您使用 memcpy()uint8_t 值的数组复制到 uint64_t 值,您的字节交换函数可以正常工作而不会出现任何可能的对齐问题:

#include <stdio.h>
#include <string.h>
#include <inttypes.h>

static inline uint64_t reverse_byte_order(uint64_t x) {
  x = (x & 0x00000000FFFFFFFF) << 32 | (x & 0xFFFFFFFF00000000) >> 32;
  x = (x & 0x0000FFFF0000FFFF) << 16 | (x & 0xFFFF0000FFFF0000) >> 16;
  x = (x & 0x00FF00FF00FF00FF) << 8 | (x & 0xFF00FF00FF00FF00) >> 8;
  return x;
}

int main(void) {
  uint8_t data[8];

  _Static_assert(sizeof(data) == sizeof(uint64_t), "You have a weird CPU");

  data[0] = 0x11;
  data[1] = 0x00;
  data[2] = 0x00;
  data[3] = 0x00;
  data[4] = 0x00;
  data[5] = 0x00;
  data[6] = 0x00;
  data[7] = 0x00;

  uint64_t num;
  memcpy(&num, data, sizeof num);
  uint64_t indata = reverse_byte_order(num);

  printf("%#" PRIx64 " reversed is %#" PRIx64 "\n", num, indata);

  return 0;
}

编译和 运行 此版本在 little-endian 系统上打印 0x11 reversed is 0x1100000000000000

注意使用 <inttypes.h> 宏为 printf() 获取正确的格式说明符。如果您在 unsigned int 是 32 位的系统上使用 %x 打印 uint64_t 值,这与您看到错误的数字有很大关系。你的编译器应该警告你这些事情。如果没有,请打开选项(-Wall -Wextra 是 gcc 和 clang 的一个很好的设置)。

显示的测试代码中的失败完全可以解释为 uint64_t 值是使用 x 转换说明符打印的,该说明符用于 unsigned。 C 标准未定义由此产生的行为,但仅转换 uint64_t 的低 32 位,因此为低 32 位中为零的值打印零是自然结果。

此问题的解决方案是使用正确的转换说明符:

  • 包括 header <inttypes.h>.
  • 代替引用字符串中的 x,结束引用字符串并使用 PRIx64。这是一个宏,它使用正确的转换说明符将 uint64_t 扩展为带引号的字符串以将其转换为十六进制,并且带引号的字符串将与相邻的字符串连接。例如,将 "\nInitial x : %016x" 更改为 "\nInitial x : %016" PRIx64

所示代码中的其他问题包括:

  • 如果 uint64_t * 的对齐方式不正确,则 C 标准未定义将指针转换为 uint64_t * 的行为。
  • 将定义为 uint8_t 数组的内存用作 uint64_t 违反了 C 2018 6.5 7 中的别名规则,并且由此产生的行为未由 C 标准定义。
  • <stdio.h> 不包括在内。
  • 行的结尾应打印 \n 而不是前面的 \n。这是因为 \n 导致 line-buffered 流被刷新:当有尾随 \n 时,立即打印输出,而只有前面的 \n,输出可以无限期地保存在缓冲区中。

将内存重新解释为另一种类型的定义方法是复制字节:

uint64_t x;
memcpy(&x, data, sizeof x);

在上面之后,x可以作为uint64_t传递给reverse_byte_order。 (由于 memcpy 是在 <string.h> 中声明的,所以 header 应该包括在内。)

还要确保在编译器中启用警告并注意它们。大多数编译器会警告格式字符串中的转换说明符与参数类型之间的不匹配,以及缺少 <stdio.h> header.