【问题标题】:Is there a way to enforce specific endianness for a C or C++ struct?有没有办法为 C 或 C++ 结构强制执行特定的字节顺序?
【发布时间】:2011-10-07 14:37:36
【问题描述】:

我见过一些关于结构字节序的问题和答案,但它们是关于检测系统的字节序,或在两种不同的字节序之间转换数据。

不过,如果有一种方法可以强制给定结构的特定字节序,我现在想做什么。除了用大量操作位域的宏重写整个事情之外,还有一些好的编译器指令或其他简单的解决方案吗?

一般的解决方案会很好,但我也会对特定的 gcc 解决方案感到满意。

编辑:

感谢所有 cmets 指出为什么强制执行字节顺序不是一个好主意,但就我而言,这正是我所需要的。

大量数据由特定处理器生成(它永远不会改变,它是具有定制硬件的嵌入式系统),并且必须由运行在未知处理器。数据的逐字节评估会非常麻烦,因为它由数百种不同类型的结构组成,这些结构又大又深:其中大多数内部都有许多其他巨大的结构层。

更改嵌入式处理器的软件是不可能的。源代码可用,这就是为什么我打算使用该系统中的结构,而不是从头开始并逐字节评估所有数据。

这就是为什么我需要告诉编译器它应该使用哪种字节序,不管它的效率如何。

不一定是真正的字节序变化。即使它只是一个接口,并且物理上一切都以处理器自己的字节序处理,我完全可以接受。

【问题讨论】:

  • 我看不出这有什么意义?您唯一关心的是在编组和解组数据时-所有其他实例,您将需要特定于平台的字节序-那么为什么要为给定结构的所有用途强制使用复杂的转换方案呢?为什么不将其完全隔离到编组/解组位?
  • 字节序不是结构的属性,而是运行代码的架构的属性。虽然理论上您可以强制内存中的表示为给定的字节序,但这将强制在所有读取和写入每个字段的所有字段中从平台转换为结构字节序,以获取从外部不可观察的内容。您应该只在处理外部世界时转换格式。
  • @Nim:我从嵌入式系统中读取了大量数据,该系统具有数百个不同的结构,其中许多内部具有其他巨大结构的深层。因为我有嵌入式系统的源代码,所以我有所有这些结构的代码。这就是为什么只使用它们会更容易的原因,因为按字节读取和评估数据需要很长时间。数据的大小和不同结构的数量巨大,所以最好避免转换。
  • @David:我与外界打交道。我知道强制字节顺序不是最干净的事情,但在这种特定情况下,使用这种特定硬件正是我所需要的。
  • 好问题。有时,有一个属性来指定结构和成员的字节顺序会非常好。类似于:__attribute__ ((endianness (BIG_ENDIAN))) 用于 gcc。许多网络协议使用 bigendian(=网络字节序)。所以协议源有很多ntohs()htonl()等调用来进行转换。如果结构中有位字段,那么代码会更难看(参见“netinet/ip.h”中的struct ip)。

标签: c++ c endianness low-level


【解决方案1】:

尝试使用
#pragma scalar_storage_order big-endian 以大端格式存储
#pragma scalar_storage_order little-endian 以小端格式存储
#pragma scalar_storage_order default 以将其存储在您的机器默认字节序中

阅读更多here

【讨论】:

    【解决方案2】:

    聚会有点晚了,但是使用当前的 GCC(在 6.2.1 和 4.9.2 上测试)终于有一种方法可以声明一个结构应该以 X-endian 字节顺序保存.

    以下测试程序:

    #include <stdio.h>
    #include <stdint.h>
    
    struct __attribute__((packed, scalar_storage_order("big-endian"))) mystruct {
        uint16_t a;
        uint32_t b;
        uint64_t c;
    };
    
    
    int main(int argc, char** argv) {
        struct mystruct bar = {.a = 0xaabb, .b = 0xff0000aa, .c = 0xabcdefaabbccddee};
    
        FILE *f = fopen("out.bin", "wb");
        size_t written = fwrite(&bar, sizeof(struct mystruct), 1, f);
        fclose(f);
    }
    

    创建一个文件“out.bin”,您可以使用十六进制编辑器(例如 hexdump -C out.bin)对其进行检查。如果支持 scalar_storage_order 属性,它将按此顺序包含预期的 0xaabbff0000aaabcdefaabbccddee 并且没有漏洞。遗憾的是,这当然是特定于编译器的。

    【讨论】:

    • 它特定于编译器的事实对于新功能来说是典型的。我不确定这是个好主意。通过使字节序不可见,我担心程序员在重要时甚至不会考虑字节序。例如,向现有数据传输协议添加校验和,该协议发送类似于您发布的代码的结构。
    • 我不确定,我喜欢把它作为类型的一个特征,我想知道如果人们把它粘在可能最终被写出的随机结构上会发生什么。也就是说,我认为标准委员会在实际上为人们提供可靠的方法来指定重要的实现要求方面做得很糟糕,并且恕我直言,在标准中具有“打包”结构等功能应该比支持 super古怪的硬件,让边缘的东西变得硬而不是普通的情况。
    【解决方案3】:

    Boost 为此提供了endian buffers

    例如:

    #include <boost/endian/buffers.hpp>
    #include <boost/static_assert.hpp>
    
    using namespace boost::endian;
    
    struct header {
        big_int32_buf_t     file_code;
        big_int32_buf_t     file_length;
        little_int32_buf_t  version;
        little_int32_buf_t  shape_type;
    };
    BOOST_STATIC_ASSERT(sizeof(h) == 16U);
    

    【讨论】:

      【解决方案4】:

      有一种称为 XDR 的数据表示。看看它。 http://en.wikipedia.org/wiki/External_Data_Representation

      虽然对于您的嵌入式系统来说可能有点过分。尝试搜索您可以使用的已经实现的库(检查许可证限制!)。

      XDR 通常用于网络系统,因为它们需要一种以独立于字节序的方式移动数据的方法。虽然没有说它不能在网络之外使用。

      【讨论】:

        【解决方案5】:

        我通常的处理方式是这样的:

        #include <arpa/inet.h> // for ntohs() etc.
        #include <stdint.h>
        
        class be_uint16_t {
        public:
                be_uint16_t() : be_val_(0) {
                }
                // Transparently cast from uint16_t
                be_uint16_t(const uint16_t &val) : be_val_(htons(val)) {
                }
                // Transparently cast to uint16_t
                operator uint16_t() const {
                        return ntohs(be_val_);
                }
        private:
                uint16_t be_val_;
        } __attribute__((packed));
        

        be_uint32_t 也是如此。

        然后你可以像这样定义你的结构:

        struct be_fixed64_t {
            be_uint32_t int_part;
            be_uint32_t frac_part;
        } __attribute__((packed));
        

        关键是编译器几乎肯定会按照您编写字段的顺序排列字段,所以您真正担心的是大端整数。 be_uint16_t 对象是一个知道如何根据需要在 big-endian 和 machine-endian 之间透明地转换自身的类。像这样:

        be_uint16_t x = 12;
        x = x + 1; // Yes, this actually works
        write(fd, &x, sizeof(x)); // writes 13 to file in big-endian form
        

        事实上,如果您使用任何相当好的 C++ 编译器编译该 sn-p,您应该会发现它会发出一个大端“13”作为常量。

        使用这些对象,内存中的表示是大端的。因此,您可以创建它们的数组,将它们放入结构中等。但是当您对它们进行操作时,它们会神奇地转换为机器端。这通常是 x86 上的一条指令,因此非常有效。在某些情况下,您必须手动投射:

        be_uint16_t x = 37;
        printf("x == %u\n", (unsigned)x); // Fails to compile without the cast
        

        ...但是对于大多数代码,您可以像使用内置类型一样使用它们。

        【讨论】:

        • +1 表示第一个解决方案,它是通用的并且实际上操纵了字节序。如果我没有找到更简单的东西,我想我会使用你的建议。谢谢!
        • 我唯一的建议可能是考虑将其声明为结构而不是类。由于您明确指定了所有成员的可访问性,因此两者在形式上是等效的。然而结构体具有轻量级的内涵,这绝对是 be_uint32_t 的目标。它还具有通常按值使用而不是按引用或按指针使用的一些含义,be_uint32_t 也是如此。另一方面 be_uint32_t 没有公共字段,这也是 struct 的含义。
        • @Kevin:是的,我通常为具有所有公共字段且没有重要功能的类保留“结构”。我想铸造运营商是否“微不足道”是值得商榷的。
        【解决方案6】:

        一个可能的创新解决方案是使用像 Ch 这样的 C 解释器并强制字节序编码变大。

        【讨论】:

          【解决方案7】:

          您可以使结构成为具有数据成员的 getter 和 setter 的类。 getter 和 setter 的实现方式如下:

          int getSomeValue( void ) const {
          #if defined( BIG_ENDIAN )
              return _value;
          #else
              return convert_to_little_endian( _value );
          #endif
          }
          
          void setSomeValue( int newValue) {
          #if defined( BIG_ENDIAN )
              _value = newValue;
          #else
              _value = convert_to_big_endian( newValue );
          #endif
          }
          

          我们有时会在从文件中读取结构时这样做 - 我们将其读入结构并在大端和小端机器上使用它来正确访问数据。

          【讨论】:

            【解决方案8】:

            我不确定是否可以修改以下内容以满足您的目的,但在我工作的地方,我们发现以下内容在很多情况下都非常有用。

            当字节序很重要时,我们使用两种不同的数据结构。一个是为了表示它预期如何到达。另一个是我们希望它如何在内存中表示。然后开发转换例程以在两者之间切换。

            工作流程就是这样运行的......

            1. 将数据读入原始结构。
            2. 将“原始结构”转换为“内存版本”
            3. 仅在“内存版本”上运行
            4. 完成操作后,将“内存中的版本”转换回“原始结构”并将其写出。

            我们发现这种解耦很有用,因为(但不限于)...

            1. 所有转化仅位于一个位置。
            2. 使用“内存中版本”时,内存对齐问题的烦恼更少。
            3. 它使从一个拱门移植到另一个拱门变得更加容易(更少的字节序问题)。

            希望这种解耦对您的应用程序也有用。

            【讨论】:

            • 感谢您的回答,我们实际上使用了类似的策略。这种情况下的问题是,结构体如此庞大、众多且复杂,以至于编写转换例程会占用大量时间。如果只有一个好的转换工具可以自动将 C 结构转换为特定的字节序!好吧,编译器指令也很好,但是一个好的接口就足够了。但是,我找不到任何东西。
            【解决方案9】:

            不,没有这样的能力。如果它存在可能导致编译器不得不生成过多/低效的代码,那么 C++ 就是不支持它。

            处理序列化的常用 C++ 方法(我假设这是您要解决的问题)这是让结构以所需的确切布局保留在内存中,并以保留字节顺序的方式进行序列化在反序列化时。

            【讨论】:

              【解决方案10】:

              不,我不这么认为。

              字节顺序是处理器的属性,指示整数是从左到右还是从右到左表示,它不是编译器的属性。

              您能做的最好的事情就是编写独立于任何字节顺序的代码。

              【讨论】:

              • 只是吹毛求疵,浮点寄存器也有位顺序。
              • @KerrekSB,只是为了挑剔,字节序是关于字节顺序,而不是位顺序。无论字节顺序如何,最高有效位始终位于字节的左侧。
              【解决方案11】:

              也许不是直接的答案,但阅读this 的问题有望回答您的一些担忧。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2011-03-28
                • 2018-02-25
                • 2022-11-02
                • 2010-09-19
                • 2013-10-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多