4

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

struct {
 char c;
 int i;
} a;

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

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

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

4

11 回答 11

14

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

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

于 2009-05-08T11:37:59.090 回答
8

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

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

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

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

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

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

于 2009-05-08T11:31:05.337 回答
5

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

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

于 2009-05-08T12:11:43.233 回答
4

对于互操作性,请查看 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上写了更多。

于 2009-05-08T12:13:12.343 回答
2

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

于 2009-05-08T19:03:11.950 回答
1

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

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

于 2009-05-08T11:30:09.380 回答
1

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

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

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

于 2009-05-08T11:40:22.517 回答
1

你可以试试preon

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

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

我的 0.02 美元

于 2009-05-08T12:52:30.813 回答
1

我强烈推荐协议缓冲区来解决这个问题。

于 2009-05-08T19:09:11.093 回答
0

据我了解,您是说您不控制 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;
}
于 2009-05-08T17:29:51.253 回答
-1

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

于 2009-05-08T11:31:27.253 回答