【问题标题】:Replicating C struct padding in Java在 Java 中复制 C 结构填充
【发布时间】:2010-10-24 18:36:24
【问题描述】:

根据here,C 编译器将在将结构写入二进制文件时填充值。正如链接中的示例所说,在编写这样的结构时:

struct {
 char c;
 int i;
} a;

对于二进制文件,编译器通常会在 char 和 int 字段之间留下一个未命名的、未使用的空洞,以确保 int 字段正确对齐。

如何使用不同的语言(在我的例子中是 Java)创建二进制输出文件(用 C 生成)的精确副本?

是否有在 Java 输出中应用 C 填充的自动方法?还是我必须通过编译器文档才能看到它是如何工作的(顺便说一下,编译器是 g++)。

【问题讨论】:

  • 值得注意的是,java 中的 char 是 16 位值,而不是 C 中的 8 位值。我建议您使用类似 ByteBuffer.putInt(0, c & 0xFF) ;顺便说一句,您知道数据是大端还是小端。在 C 中,这取决于您正在运行的机器的架构。

标签: java c compiler-construction padding


【解决方案1】:

这个洞是可配置的,编译器有开关来将结构对齐 1/2/4/8 字节。

所以第一个问题是:你到底想模拟哪种对齐方式?

【讨论】:

    【解决方案2】:

    不仅在写入文件时如此,在内存中也是如此。如果结构是逐字节写出的,那么结构在内存中填充的事实会导致文件中出现填充。

    通常很难准确地复制确切的填充方案,尽管我想一些启发式方法会让你走得很远。如果您有 struct 声明以供分析,它会有所帮助。

    通常,大于一个字符的字段将被对齐,以便它们在结构内的起始偏移量是它们大小的倍数。这意味着shorts 通常会在偶数偏移上(可被 2 整除,假设 sizeof (short) == 2),而doubles 将在可被 8 整除的偏移上,依此类推。

    更新:出于这样的原因(以及与字节序有关的原因),将整个结构转储到文件中通常是一个坏主意。最好逐个字段进行,如下所示:

    put_char(out, a.c);
    put_int(out, a.i);
    

    假设put-functions 只写入值所需的字节,这将向文件发出结构的无填充版本,从而解决问题。也可以通过相应地编写这些函数来确保正确的、已知的字节顺序。

    【讨论】:

    • 谢谢,不幸的是,我无法更改 C 结构输出到文件的方式。通过以您提到的形式应用简单的填充,我能够使用 java 获得相同的输出。你知道 C 编译器如何实现填充之间是否存在很大差异??
    • 它主要取决于处理器架构,因此一旦将其移植到其他架构(我们最近就有),“乐趣”就真正开始了。这并不意味着在同一架构上的编译器之间可以保证相同。
    • 这里谈到了一些关于结构打包以及如何以及为什么应用填充:catb.org/esr/structure-packing
    【解决方案3】:

    您可以更改 c 端的打包以确保不使用填充,或者您可以在十六进制编辑器中查看生成的文件格式,以允许您在 Java 中编写一个忽略填充字节的解析器。

    【讨论】:

      【解决方案4】:

      不要这样做,它很脆弱,会导致对齐和字节序错误。

      对于外部数据,最好以字节为单位显式定义格式,并编写显式函数以使用移位和掩码(不是联合!)在内部和外部格式之间进行转换。

      【讨论】:

      • XML 可能在这个地方有一席之地?
      • @starblue XML 必须是所有问题的解决方案!!1
      • @starblue 感谢您的回答。编写显式文件转换实际上是不可能的,因为需要使用 Java 输出许多 C 结构(并且这些结构的格式可能会不时改变)。我觉得我可能必须实现与 g++ 在 Java 中输出时使用的相同形式的填充(我同意你的观点,这很脆弱)。唯一的优点是平台和编译器不会改变,所以字节序字节大小等也不应该改变
      • 您也可以尝试通过添加 attribute__((__packed)) 来打包结构。另一种解决方案是为正确的 I/O 功能编写代码生成器。
      • @Bombe 你有没有考虑过 xml 中的 10 兆像素 HDR 图像 LOL。一个好的文件格式应该考虑与大多数目标处理器相同的字节序。填充规则也是如此。 Windows 位图中的扫描线总是从 4 字节边界开始。在整个过程中保持二进制兼容会加快速度。
      【解决方案5】:

      对于 Java,数据类型的大小由语言规范定义。例如,byte 类型为 1 个字节,short 为 2 个字节,依此类推。这与 C 不同,C 中每种类型的大小取决于架构。

      因此,了解二进制文件的格式以便能够将文件读入 Java 非常重要。

      可能需要采取措施来确定字段是特定大小,以解决编译器或体系结构的差异。提到对齐似乎表明输出文件将取决于架构。

      【讨论】:

        【解决方案6】:

        是否有自动应用 C 的方法 Java输出中的填充?还是我有 浏览编译器文档 看看它是如何工作的(编译器是 顺便说一下g++)。

        两者都不是。相反,您明确指定数据/通信格式并实现该规范,而不是依赖 C 编译器的实现细节。您甚至不会从不同的 C 编译器获得相同的输出。

        【讨论】:

          【解决方案7】:

          关于互操作性,请查看 ByteBuffer 类。

          本质上,你创建一个一定大小的缓冲区,在不同位置放置不同类型的变量,然后在最后调用 array() 来检索“原始”数据表示:

          ByteBuffer bb = ByteBuffer.allocate(8);
          bb.order(ByteOrder.LITTLE_ENDIAN);
          bb.put(0, someChar);
          bb.put(4, someInteger);
          byte[] rawBytes = bb.array();
          

          但由您决定在哪里放置填充 - 即在位置之间跳过多少字节。

          为了读取从 C 写入的数据,您通常 wrap() 一个 ByteBuffer 围绕您从文件中读取的某个字节数组。

          如果有帮助,我在ByteBuffer 上写了更多内容。

          【讨论】:

          • 是的,我一直在使用 ByteBuffer。我真正遇到的问题是找出在写/读时要填充多少字节..
          【解决方案8】:

          你可以试试preon

          Preon 是一个 java 库,用于为比特流压缩数据构建编解码器。 声明式(基于注释)的方式。想想 JAXB 或 Hibernate,然后是二进制 编码数据。

          它可以处理 Big/Little endian 二进制数据、对齐(填充)和各种数字类型以及其他功能。这是一个非常好的图书馆,我非常喜欢它

          我的 0.02 美元

          【讨论】:

            【解决方案9】:

            据我了解,您是说您不控制 C 程序的输出。你必须接受它。

            那么对于某些特定的结构集,您是否必须阅读此文件,或者您是否必须在一般情况下解决这个问题?我的意思是,有人说“这是程序 X 创建的文件,你必须用 Java 读取它”的问题吗?还是他们希望您的 Java 程序读取 C 源代码,找到结构定义,然后用 Java 读取它?

            如果您有一个特定的文件要读取,那么问题并不是很困难。通过查看 C 编译器规范或研究示例文件,找出填充的位置。然后在 Java 端,将文件作为字节流读取,并构建您知道即将到来的值。基本上我会编写一组函数来从 InputStream 读取所需的字节数并将它们转换为适当的数据类型。喜欢:

            int readInt(InputStream is,int len)
              throws PrematureEndOfDataException
            {
              int n=0;
              while (len-->0)
              {
                int i=is.read();
                if (i==-1)
                  throw new PrematureEndOfDataException();
                byte b=(byte) i;
                n=(n<<8)+b;
              }
              return n;
            }
            

            【讨论】:

              【解决方案10】:

              在 Java 中读取/写入 C 结构的一种便捷方法是使用 javolution Struct 类(请参阅http://www.javolution.org)。这不会帮助您自动填充/对齐数据,但它确实使处理保存在 ByteBuffer 中的原始数据更加方便。如果您不熟悉 javolution,那么值得一看,因为里面还有很多其他很酷的东西。

              【讨论】:

                【解决方案11】:

                我强烈推荐 protocol buffers 来解决这个问题。

                【讨论】:

                  猜你喜欢
                  • 2015-04-25
                  • 2015-07-02
                  • 1970-01-01
                  • 2011-10-21
                  • 2023-04-03
                  • 1970-01-01
                  • 2014-10-12
                  • 2016-04-13
                  • 2017-06-08
                  相关资源
                  最近更新 更多