【问题标题】:How do I convert a value from host byte order to little endian?如何将值从主机字节顺序转换为小端?
【发布时间】:2010-12-24 19:21:43
【问题描述】:

我需要将一个短值从主机字节顺序转换为小端。如果目标是大端,我可以使用 htons() 函数,但可惜 - 它不是。

我想我能做到:

swap(htons(val))

但这可能会导致字节被交换两次,从而使结果正确,但会给我带来性能损失,这在我的情况下是不行的。

【问题讨论】:

  • 不用担心性能损失。如果你做了多余的事情(交换一个 int 两次),编译器会检测到它并在优化阶段删除代码。
  • Nils 所说的,但更谨慎的方法是首先检查生成的代码在你能承受的优化级别上(如果你被调试卡住了,那么哦)。如果双重交换被优化掉,你的性能问题就会立即解决。
  • 编译器真的能优化这个吗?我想如果 swap() 和 htons() 是宏或内联函数,它会,但否则呢?
  • 视情况而定。有时编译器可以自动内联(或通过链接时代码生成间接内联),有时必须提示使用(强制)内联或使用跨模块编译......如果不先尝试就很难获得准确的建议。

标签: c++ c endianness


【解决方案1】:

这是一篇来自 IBM 的关于字节序以及如何确定字节序的文章:

Writing endian-independent code in C: Don't let endianness "byte" you

它包含一个如何在运行时确定字节顺序的示例(您只需要做一次)

const int i = 1;
#define is_bigendian() ( (*(char*)&i) == 0 )

int main(void) {
    int val;
    char *ptr;
    ptr = (char*) &val;
    val = 0x12345678;
    if (is_bigendian()) {
        printf(“%X.%X.%X.%X\n", u.c[0], u.c[1], u.c[2], u.c[3]);
    } else {
        printf(“%X.%X.%X.%X\n", u.c[3], u.c[2], u.c[1], u.c[0]);
    }
    exit(0);
}

该页面还有一个关于反转字节顺序的方法的部分:

short reverseShort (short s) {
    unsigned char c1, c2;

    if (is_bigendian()) {
        return s;
    } else {
        c1 = s & 255;
        c2 = (s >> 8) & 255;

        return (c1 << 8) + c2;
    }
}

;

short reverseShort (char *c) {
    short s;
    char *p = (char *)&s;

    if (is_bigendian()) {
        p[0] = c[0];
        p[1] = c[1];
    } else {
        p[0] = c[1];
        p[1] = c[0];
    }

    return s;
}

【讨论】:

  • 检查条件是否比进行额外交换更好?
  • 这样做的目的是 1. 使代码可移植,以及 2. 只在不是小端序的平台上进行交换。在系统已经是小端的情况下,您最终会进行一次测试并跳转与 4 字节交换。
  • 另一件事,因为条件永远不会改变#define is_bigendian() ( (*(char*)&amp;i) == 0 ),我猜cpu上的分支预测器可能会消除它,导致当系统已经是小端时这实际上变成了一个noop。
  • 这项技术的好主意是它可以在没有 ntohs() 函数的系统上工作。此外,swap 通常只适用于两个参数。此方法允许扩展为超过 2 个八位字节的整数宽度。
  • 在reverseShort() 的第一个实现中,c1 应该在第二个return 语句中转换为short,然后再移位。否则,他们最终只会走向天空中的大桶。
【解决方案2】:

那么你应该知道你的字节顺序并有条件地调用 htons() 。实际上,甚至不是 hton,而是有条件地交换字节。当然是编译时。

【讨论】:

  • +1。如果您对表演感到汗流浃背,#ifdef 就是您的朋友。
【解决方案3】:

类似于以下内容:

unsigned short swaps( unsigned short val)
{
    return ((val & 0xff) << 8) | ((val & 0xff00) >> 8);
}

/* host to little endian */

#define PLATFORM_IS_BIG_ENDIAN 1
#if PLATFORM_IS_LITTLE_ENDIAN
unsigned short htoles( unsigned short val)
{
    /* no-op on a little endian platform */
    return val;
}
#elif PLATFORM_IS_BIG_ENDIAN
unsigned short htoles( unsigned short val)
{
    /* need to swap bytes on a big endian platform */
    return swaps( val);
}
#else
unsigned short htoles( unsigned short val)
{
    /* the platform hasn't been properly configured for the */
    /* preprocessor to know if it's little or big endian    */

    /* use potentially less-performant, but always works option */

    return swaps( htons(val));
}
#endif

如果您的系统配置正确(这样预处理器可以知道目标 id 是小端还是大端),您将获得 htoles() 的“优化”版本。否则,您将获得依赖于htons() 的潜在非优化版本。无论如何,你会得到一些有用的东西。

没有什么太棘手的,或多或少的便携性。

当然,您可以通过使用inline 或您认为合适的宏来进一步提高优化可能性。

对于定义各种编译器字节序的实际实现,您可能希望查看类似“便携式开源线束 (POSH)”的内容。请注意,进入图书馆需要通过伪身份验证页面(尽管您无需注册即可提供任何个人详细信息):http://hookatooka.com/poshlib/

【讨论】:

  • 正是我想要的。我在 Linux/gcc 环境中,因此通过包含 ,__BYTE_ORDER 被定义为 __LITTLE_ENDIAN 或 __BIG_ENDIAN(或 __PDP_ENDIAN)
【解决方案4】:

这个技巧应该是:在启动时,使用带有虚拟值的ntohs,然后将结果值与原始值进行比较。如果两个值相同,则机器使用大端,否则使用小端。

然后,根据初始测试的结果,使用不执行任何操作或调用 ntohsToLittleEndian 方法。

(根据 cmets 提供的信息进行编辑)

【讨论】:

  • 您是否注意到 OP 担心性能损失? ;-)
  • OP 只需要在启动时进行一次检查。
  • flyfishr64,每次检查结果。永远不要将编译时可以做的事情推迟到运行时。
【解决方案5】:

我的经验法则是,这取决于您是一次性对一大块数据进行小端处理,还是只对一个值进行处理:

如果只有一个值,那么函数调用开销可能会淹没不必要的字节交换的开销,即使编译器没有优化掉不必要的字节交换也是如此。然后,您可能会将值写入套接字连接的端口号,并尝试打开或绑定套接字,与任何类型的位操作相比,这需要一段时间。所以别担心。

如果是一个大块,那么您可能会担心编译器无法处理它。所以做这样的事情:

if (!is_little_endian()) {
    for (int i = 0; i < size; ++i) {
        vals[i] = swap_short(vals[i]);
    }
}

或者查看你的架构上的 SIMD 指令,它可以更快地完成。

用你喜欢的任何技巧写is_little_endian()。我认为 Robert S. Barnes 提供的是合理的,但是由于您通常知道给定目标是大端还是小端,也许您应该有一个特定于平台的头文件,将其定义为宏评估为 1 或 0。

与往常一样,如果您真的关心性能,那么请查看生成的程序集,看看是否已删除无意义的代码,然后对各种替代方案进行时间对比,以查看实际运行速度最快的方案。

【讨论】:

    【解决方案6】:

    不幸的是,没有真正的跨平台方法可以在编译时使用标准 C 来确定系统的字节顺序。我建议在您的 config.h 中添加一个 #define(或者您或您的构建系统使用的任何其他内容)构建配置)。

    检查LITTLE_ENDIANBIG_ENDIAN 的正确定义的单元测试可能如下所示:

    #include <assert.h>
    #include <limits.h>
    #include <stdint.h>
    
    void check_bits_per_byte(void)
    { assert(CHAR_BIT == 8); }
    
    void check_sizeof_uint32(void)
    { assert(sizeof (uint32_t) == 4); }
    
    void check_byte_order(void)
    {
        static const union { unsigned char bytes[4]; uint32_t value; } byte_order =
            { { 1, 2, 3, 4 } };
    
        static const uint32_t little_endian = 0x04030201ul;
        static const uint32_t big_endian = 0x01020304ul;
    
        #ifdef LITTLE_ENDIAN
        assert(byte_order.value == little_endian);
        #endif
    
        #ifdef BIG_ENDIAN
        assert(byte_order.value == big_endian);
        #endif
    
        #if !defined LITTLE_ENDIAN && !defined BIG_ENDIAN
        assert(!"byte order unknown or unsupported");
        #endif
    }
    
    int main(void)
    {
        check_bits_per_byte();
        check_sizeof_uint32();
        check_byte_order();
    }
    

    【讨论】:

      【解决方案7】:

      在许多 Linux 系统上,有一个 &lt;endian.h&gt;&lt;sys/endian.h&gt; 具有转换功能。 man page for ENDIAN(3)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-01-27
        • 1970-01-01
        • 2013-12-30
        • 2014-06-28
        • 1970-01-01
        • 2015-06-28
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多