【问题标题】:C - Big-endian struct interconvert with little-endian structC - 大端结构与小端结构相互转换
【发布时间】:2018-12-26 04:24:48
【问题描述】:

我有两个具有相同数据成员的结构。 (一个是 big_endian 结构,另一个是 little_endian )现在我必须与它们相互转换。但是当我编码时,我发现有很多重复的代码,几乎没有变化。在没有重复代码的情况下,如何将这些代码更改为更优雅? (重复代码意味着这些代码可能相似,例如mode == 1mode == 2,它们只是赋值位置不同。看起来不优雅但有效。)

这是我的代码:

#pragma scalar_storage_order big-endian

typedef struct {   
    int a1;    
    short a2;    
    char a3;    
    int a4;    
} test_B;

#pragma scalar_storage_order default

typedef struct {
    int a1;    
    short a2;    
    char a3;    
    int a4;    
} test_L;


void interconvert(test_L *little, test_B *big, int mode) {
    // if mode == 1 , convert little to big    
    // if mode == 2 , convert big to little    
    // it may be difficult and redundant when the struct has lots of data member!    
    if(mode == 1) {
        big->a1 = little->a1;
        big->a2 = little->a2;
        big->a3 = little->a3;
        big->a4 = little->a4;
    }
    else if(mode == 2) {
        little->a1 = big->a1;
        little->a2 = big->a2;
        little->a3 = big->a3;
        little->a4 = big->a4;
    }
    else return;
}

注意:以上代码必须在gcc-7或更高版本上运行,因为#pragma scalar_storage_order

【问题讨论】:

  • 我认为这没有任何问题。也许你可以创建一个单独的函数little_to_big(test_L *little, test_B *big)big_to_little(test_B *big, test_L *little)。我经常有超过 4 个字段的类,并且没有其他方法可以分配/初始化它们。附带说明一下,如果您的函数名称由多个单词组成,请使用蛇形或骆驼式大小写来区分它们。
  • 您在这个问题中使用了奇怪的字符,例如 ...这是一件小事,但如果你能解决它,我认为问题会更容易一些读书。另外,也许我遗漏了一些明显的东西,但是“重复代码”是什么意思?谢谢,欢迎来到 SO!
  • “重复代码”表示这些代码可能相似,例如 mode==1 & mode==2 ,只是分配位置不同。看起来不优雅但有效
  • 你看过Wikipedia - Endianness——它还提供了一个用于在大/小端之间转换整数值的C函数? (您可以轻松适应 short 值和 char 什么都不需要。)
  • 这个问题存在一个根本性的潜在错误:试图以可移植的方式使用结构来描述二进制数据格式。相反,您应该编写打包/解包函数来在二进制数据缓冲区和内部、本机格式数据(根本不需要类似于二进制数据存储)之间进行转换。是的,我知道很多人都这样做。我的观点是它是不可移植的,并且难以维护。和一个简单的错误来源。您是否想要编写有缺陷的代码或您可以信任的代码?

标签: c gcc optimization struct


【解决方案1】:

发布了一个建议使用 memcpy 解决此问题的答案,但该答案已被删除。其实这个答案是对的,如果使用得当的话,我想解释一下原因。

正如他所指出的,OP 指定的#pragma 是核心:

注意:由于#pragma scalar_storage_order,上述代码必须在gcc-7或更高版本上运行

来自 OP 的结构:

#pragma scalar_storage_order big-endian
typedef struct {   
    int a1;    
    short a2;    
    char a3;    
    int a4;    
} test_B;

表示指令“test_B.a2=256”在属于a2成员的两个连续字节中写入,分别为1和0。这是big-endian。类似的指令“test_L.a2=256”将改为存储字节 0 和 1(小端序)。

下面的memcpy:

memcpy(&test_L, &test_B, sizeof test_L)

会使 test_L.a2 的字节等于 1 和 0,因为那是 test_B.a2 的 ram 内容。但是现在,以小端模式读取 test_L.a2,这两个字节表示 1。我们写入 256 并读回 1。这正是想要的转换。

要正确使用此机制,只需写入一个结构,将 memcpy() 写入另一个结构,然后逐个成员读取另一个结构就足够了。大端变成小端,反之亦然。当然,如果打算详细说明数据并对其应用计算,那么了解数据的字节序很重要;如果它与默认模式匹配,则在计算之前不必进行任何转换,但必须稍后应用转换。相反,如果传入的数据与处理器的“默认字节序”不匹配,则必须先进行转换。


编辑 在下面的OP评论之后,我进行了更多调查。我看了一下这个https://gcc.gnu.org/onlinedocs/gcc/Structure-Layout-Pragmas.html

嗯,有三种#pragma 可供选择字节布局:big-endianlittle-endiandefault。前两个之一等于最后一个:如果目标机器是little-endian,则default表示little-endian;如果是 big-endian,则默认为 big-endian。这不仅合乎逻辑。

所以,在 big-endian 和 default 之间执行 memcpy() 在 big-endian 机器上没有任何作用;这也是合乎逻辑的。好的,我更强调 memcpy() 本身绝对没有做任何事情:它只会将数据从以某种方式处理的 ram 区域移动到以另一种方式处理的另一个区域。这两个不同的区域只有在正常的成员访问完成时才会被区别对待:这里来玩#pragma scalar_storage_order。正如我之前写的,重要的是要知道哪些字节序有数据进入程序。例如,如果它们来自 TCP 网络,我们知道那是大端;更一般地说,如果它来自“程序”之外并尊重协议,我们应该知道字节序有什么。

要将字节序转换为另一种字节序,应该使用 littlebigNOT 默认值,因为 default 肯定等于前两者之一。


还有一个修改

受到 cmets 和使用在线编译器的 Jamesdlin 的刺激,我也尝试这样做。在这个网址http://tpcg.io/lLe5EW 有一个演示,分配给一个结构的成员,memcpy 给另一个,然后读取,字节序转换就完成了。就是这样。

【讨论】:

  • 很抱歉地告诉你,memcpy 函数在 Mac OS 上运行的正是我们想要的,Apple LLVM 版本 10.0.0 (clang-1000.11.45.5)。但在 ubuntu(gcc-7) 和 Mac OS(gcc-7) 上都没有。
  • @b.bkb memcpy 至少在一种情况下有效的事实意味着这个想法并非完全错误。进一步分析可以说明其他两个系统出了什么问题,但我必须问:数据来自哪里?精化后的数据输出如何?各种系统的字节顺序是什么?
  • 对不起,但这(以及您对已删除答案的 cmets)没有意义。 memcpy 需要 void* 和要复制的字节数。它对所指向的内容一无所知,它是否是structstruct 包含哪些成员等等。我愿意 愿意相信的是在某些情况下,编译器可能会通过内联成员分配来优化 memcpy of struct 指针。
  • 但是,当然,如果是这样的话,那将是一个编译器错误,因为优化不应该改变行为。我很想知道memcpy 方法在禁用所有优化的情况下是否“有效”。
  • @jamesdlin 这个技巧根本不是由 memcpy() 制造的:该函数只需要盲目地逐字复制一个字节序列,它就是这样做的。诀窍是编译器对两种结构的处理方式不同,这要归功于选择字节序的#pragmas。它类似于使用 memcpy 复制 4 个字节,从 uint32 到 int32:复制后,int32 可以为负数,即使 uint32(当然)不是。相同的位模式有不同的解释......在这种情况下,两个结构是相同的:相同的位模式意味着不同的东西。
猜你喜欢
  • 2010-10-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-11-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多