struct {
char c;
int i;
} a;
当编译器将数据写入二进制文件时,通常会在 char 和 int 字段之间留下一个未命名的、未使用的空白区域,以确保 int 字段被正确对齐。
我该如何使用另一种语言(比如 Java)创建与 C 生成的二进制输出文件完全相同的副本呢?
是否有自动方式可以在 Java 输出中应用 C 填充?还是我需要查看编译器文档来了解其工作原理(这里使用的编译器是 g++)?
struct {
char c;
int i;
} a;
当编译器将数据写入二进制文件时,通常会在 char 和 int 字段之间留下一个未命名的、未使用的空白区域,以确保 int 字段被正确对齐。
我该如何使用另一种语言(比如 Java)创建与 C 生成的二进制输出文件完全相同的副本呢?
是否有自动方式可以在 Java 输出中应用 C 填充?还是我需要查看编译器文档来了解其工作原理(这里使用的编译器是 g++)?
不要这样做,它很脆弱,会导致对齐和字节顺序的问题。
对于外部数据而言,更好的方法是明确定义以字节为单位的格式,并编写显式函数来在内部和外部格式之间进行转换,使用移位和掩码(而不是 union!)。
short
通常位于偶数偏移量上(假设sizeof(short)==2
),而double
等则位于可被8整除的偏移量上。
更新:正因为这样的原因(以及与字节顺序有关的原因),通常不建议将整个结构体转储到文件中。最好是按字段进行操作,如下所示:put_char(out, a.c);
put_int(out, a.i);
put
函数只写入值所需的字节,则这将向文件发出无填充版本的结构体,从而解决了问题。通过适当编写这些函数,还可以确保正确、已知的字节顺序。为了实现互操作性,可以查看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语言编写的数据,则通常需要将 ByteBuffer 包装在从文件中读取的某些字节数组周围。
如果有帮助的话,我在ByteBuffer上写了更多内容。
使用javolution Struct类(请参见http://www.javolution.org)是在Java中读取/写入C结构的方便方法。这不会帮助您自动填充/对齐数据,但它确实使在ByteBuffer中持有的原始数据更加方便地处理。如果您不熟悉javolution,则值得一看,因为其中还有很多其他很酷的东西。
这个空洞是可配置的,编译器有开关可以将结构体对齐为1/2/4/8个字节。
因此,第一个问题是:您想要模拟哪种精确的对齐方式?
使用Java,数据类型的大小由语言规范定义。例如,byte
类型为1个字节,short
为2个字节,依此类推。这与C不同,C中每种类型的大小都依赖于体系结构。
因此,重要的是要了解二进制文件的格式,以便能够将文件读入Java。
可能需要采取措施来确保字段具有特定的大小,以解决编译器或体系结构之间的差异。对齐方式的提及似乎意味着输出文件将取决于体系结构。
你可以尝试preon:
Preon是一个Java库,用于以声明式(基于注释)的方式构建位流压缩数据的编解码器。类似于JAXB或Hibernate,但适用于二进制编码数据。
它可以处理大小端二进制数据、对齐(填充)和各种数字类型等其他功能。这是一个非常不错的库,我非常喜欢它。
我的0.02美元。
据我理解,您的意思是说您无法控制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;
}