我最近使用C / C ++加载了很多二进制文件,而且我对它的优雅程度感到困惑。要么我得到很多看起来像这样的代码(我已经开始了):
uint32_t type, k;
uint32_t *variable;
FILE *f;
if (!fread(&type, 4, 1, f))
goto boundsError;
if (!fread(&k, 4, 1, f))
goto boundsError;
variable = malloc(4 * k);
if (!fread(variable, 4 * k, 1, f))
goto boundsError;
或者,我定义了一个本地的压缩结构,这样我就可以更容易地读取常量大小的块。然而,在我看来,对于这样一个简单的问题 - 即将指定文件读入内存 - 可以更有效地以更可读的方式完成。有没有人有任何提示/技巧等?我想澄清一点,我不是在寻找一个图书馆或其他东西来处理这个问题。如果我设计自己的文件并且不得不更改文件规格,我可能会受到诱惑,但是现在我只是在寻找风格的答案。
另外,有些人可能会建议mmap
- 我喜欢mmap!我经常使用它,但它的问题在于它导致了处理未对齐数据类型的讨厌代码,这在使用stdio时并不存在。最后,我将编写类似stdio的包装函数,用于从内存中读取。
谢谢!
编辑:我还应该澄清一下,我无法更改文件格式 - 我必须阅读一个二进制文件;我无法以其他格式请求数据。答案 0 :(得分:3)
我见过这个问题的最优雅的解决方案是Sean Barrett的writefv
,用于他的小图像编写库stb_image_write
here。他只实现了一些原语(并且没有错误处理),但是同样的方法可以扩展到基本上是二进制printf
(并且对于读取,你可以做同样的方法来获得二进制scanf
)。非常优雅和整洁!事实上,整个过程非常简单,我不妨在此处加入:
static void writefv(FILE *f, const char *fmt, va_list v)
{
while (*fmt) {
switch (*fmt++) {
case ' ': break;
case '1': { unsigned char x = (unsigned char) va_arg(v, int); fputc(x,f); break; }
case '2': { int x = va_arg(v,int); unsigned char b[2];
b[0] = (unsigned char) x; b[1] = (unsigned char) (x>>8);
fwrite(b,2,1,f); break; }
case '4': { stbiw_uint32 x = va_arg(v,int); unsigned char b[4];
b[0]=(unsigned char)x; b[1]=(unsigned char)(x>>8);
b[2]=(unsigned char)(x>>16); b[3]=(unsigned char)(x>>24);
fwrite(b,4,1,f); break; }
default:
assert(0);
return;
}
}
}
以下是他如何使用它编写truecolor .BMP文件:
static int outfile(char const *filename, int rgb_dir, int vdir, int x, int y, int comp, void *data, int alpha, int pad, const char *fmt, ...)
{
FILE *f;
if (y < 0 || x < 0) return 0;
f = fopen(filename, "wb");
if (f) {
va_list v;
va_start(v, fmt);
writefv(f, fmt, v);
va_end(v);
write_pixels(f,rgb_dir,vdir,x,y,comp,data,alpha,pad);
fclose(f);
}
return f != NULL;
}
int stbi_write_bmp(char const *filename, int x, int y, int comp, const void *data)
{
int pad = (-x*3) & 3;
return outfile(filename,-1,-1,x,y,comp,(void *) data,0,pad,
"11 4 22 4" "4 44 22 444444",
'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40, // file header
40, x,y, 1,24, 0,0,0,0,0,0); // bitmap header
}
(write_pixels
的定义已被删除,因为它在这里很切线)
答案 1 :(得分:1)
如果要反序列化二进制数据,可以选择为要使用的结构定义序列化宏。使用模板函数和流,C ++中的 lot 更容易。 (boost :: serialization是一个非侵入式序列化库,但如果你想要打扰,你可以让它更优雅)
简单C宏:
#define INT(f,v) \
{ int _t; fread(&_t, sizeof(int), 1, f); v = ntohl(_t); }
#define FLOAT(f,v) \
{ int _t; fread(&_t, sizeof(int), 1, f); v = ntohl(_t); /* type punning */ memcpy(&v, &_t, sizeof(float)); }
...
用法:
int a;
float b;
FILE *f = fopen("file", "rb");
INT(f, a);
FLOAT(f, b);
而且,是的,序列化代码是一些最无聊和脑死的代码。如果可以,请使用元数据描述您的数据结构,并以机械方式生成代码。有一些工具和库可以帮助解决这个问题,或者您可以使用Perl或Python或PowerShell或其他任何方式自行编写。
答案 2 :(得分:0)
我会通过重构一下来使你的代码看起来不那么优雅,所以你的复杂数据结构会通过一系列基础类型的调用来读取。
我假设您的代码是纯C而不是C ++,因为在后者中您可能会抛出异常而不是使用goto语句。
答案 3 :(得分:0)
数组读取部分看起来应该具有自己的可重用功能。除此之外,如果你真的有C ++可用(问题并不完全清楚),那么硬编码变量的大小是不必要的,因为大小可以从指针中推断出来。
template<typename T>
bool read( FILE* const f, T* const p, size_t const n = 1 )
{
return n * sizeof(T) == fread(f, sizeof T, n, p);
}
template<typename T>
bool read( FILE* const f, T& result )
{
return read(f, &result);
}
template<typename Tcount, typename Telement>
bool read_counted_array( FILE* const f, Tcount& n, Telement*& p )
{
if (!read(f, n) || !(p = new Telement[n]))
return false;
if (read(f, p, n))
return true;
delete[] p;
p = 0;
return false;
}
然后
uint32_t type, k;
uint32_t *variable;
FILE *f;
if (read(f, type) &&
read_counted_array(f, k, variable) && ...
) {
//...
}
else
goto boundsError;
当然,如果将数据传递给假定{{1}的代码,请随时继续使用malloc
和free
代替new[]
和delete[]
使用了。
答案 4 :(得分:0)
以下是我提出的一些C99代码:
你的例子如下:
#include "read_values.h"
#include "read_array.h"
assert(sizeof (uint32_t) == 4);
uint32_t type, k;
uint32_t *variable;
FILE *f;
_Bool success =
read_values(f, "c4c4", &type, &k) &&
read_array(f, variable, k);
if(!success)
{
/* ... */
}
答案 5 :(得分:-1)
您可能对protocol buffers和其他IDL计划感兴趣。