【问题标题】:Comfortable way to use big and little endian at the same time?同时使用大端和小端的舒适方式?
【发布时间】:2018-05-04 09:16:16
【问题描述】:

我的编译器支持所谓的“类型限定符”__bigendian__littleendian
我的变量Buffer 看起来像这样:

static union
{
    uint8 as_byte[10];
    __bigendian uint16 as_big_endian[5];
    uint16 as_little_endian[5];
} Buffer;

在我的情况下,微控制器是小端格式,但是对于某些通信接口,我需要大端格式的 16 位单元。
微控制器支持指令byte swap,在使用这些类型限定符的情况下编译该指令,所以实际上这似乎是一种舒适的方式。
编译器手册说我应该避免使用“类型限定符”,因为可移植性问题 - 当然!

我只是想知道是否有比使用字节访问和根据目标的字节序手动进行交换更好的方法?

【问题讨论】:

  • 这是 C 还是 C++?请记住,对于 C++,访问未最后分配的联合中的变量是 UB。
  • union 不会为你交换字节
  • @Anton Malyshev:正确,但我的想法是使用“小端”元素的简单分配给“大端”元素或反之亦然比使用位移更有效,因为“字节交换”指令。
  • 如果您使用 C++ 编写代码,请查看 boost endian 库。

标签: c++ endianness


【解决方案1】:

有或多或少的标准函数ntohl(32 位)、ntohs(16 位)及其对应的htonlhtons

这些函数分别从网络到主机端和主机到网络端转换。

网络端总是大端。

主机字节序取决于您的目标。

这些函数通常使用您注释的指令(字节交换等)实现,并使用编译器预处理器指令启用它们或只是 noop 的。

您的编译器可能已经支持它们。

但例如,典型的实现如下所示:

#if TARGET_IS_LITTLE_ENDIAN
    static inline uint32_t ntohl(uint32_t x) {
        //Do actual 32 bit swap
        return swap32(x);
    }
    static inline uint16_t ntohs(uint16_t x) {
        //Do actual 16 bit swap
        return swap16(x);
    }
#else
    #define ntohl(x) (x)
    #define ntohs(x) (x)
#endif

#define htonl(x) ntohs(x)
#define htons(x) ntohs(x)

注意只需要实现一对函数,因为另一个是一样的,只是语法糖。

最后,你只在实际的接收/发送函数中调用这些函数。 在程序的任何其他部分,您只能在主机端工作。

此外,基于这些函数,您可以轻松实现自定义ntoh_structure 函数:

ntoh_struct(my_struct *st) {
    st->fieldA=ntohl(st->fieldA);
    st->fieldB=ntohl(st->fieldB);
}
#define hton_struct(st) ntoh_struct(st)

同样,它只会在实际接收/传输数据时使用。


请注意,函数 swapXX 的实现取决于目标和编译器。

对于C 语言,遵循您的示例(实际上我以前从未见过)的实现如下:

typedef union {
    __bigendian uint16_t be;
    uint16_t le;
} Buffer16;
typedef union {
    __bigendian uint32_t be;
    uint32_t le;
} Buffer32;

static inline uint16_t swap16(uint16_t x) {
    Buffer16 aux;
    aux.be=x;
    return aux.le;
}
static inline uint32_t swap32(uint32_t x) {
    Buffer32 aux;
    aux.be=x;
    return aux.le;
}

【讨论】:

    【解决方案2】:

    最好的解决方案是根本不使用联合,而是定义uint8_t byte[10];的字节顺序。如果你决定说这个数组的字节顺序例如是

    [0]=MSB0 [1]=LSB0 [2]=MSB1 [3]=LSB1... 
    

    而且 CPU 字节序是该死的,然后您可以便携地访问它:

    size_t index = i*2; // assuming i is 0 to 5
    uint16_t word = (uint16_t)byte[index] << 8 | 
                    (uint16_t)byte[index+1];
    

    word 现在在正确的位置包含 MSB 和 LSB,无论 CPU 字节顺序如何。代码是可移植的。这里的关键是使用位移,它们优于任何其他方法(联合、指针运算等)。

    至于通过已知大字节序的协议发送它,只需按预期顺序获取字节即可。在这种情况下,一个简单的for(size_t i=0; i&lt;10; i++)

    【讨论】:

      【解决方案3】:

      协议缓冲区可以解决字节序问题,但它有内存/处理开销。 请参考https://developers.google.com/protocol-buffers/

      【讨论】:

        猜你喜欢
        • 2011-06-12
        • 2021-04-09
        • 2011-12-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-02-04
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多