Элегантный бинарный ввод / вывод в C? - PullRequest
1 голос
/ 14 ноября 2010

Я недавно загружал много бинарных файлов, используя 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 -Я люблю ММАП!Я часто его использую, но проблема в том, что он приводит к неприятному коду для обработки невыровненных типов данных, которого на самом деле не существует при использовании stdio.В конце я бы написал функции-оболочки типа stdio для чтения из памяти.

Спасибо!

РЕДАКТИРОВАТЬ: я также должен уточнить, что я не могу изменить форматы файлов - естьдвоичный файл, который я должен прочитать;Я не могу запросить данные в другом формате.

Ответы [ 6 ]

3 голосов
/ 14 ноября 2010

Самое элегантное решение, которое я видел для этой проблемы, - это writefv Шона Барретта, использованный в его крошечной библиотеке для записи изображений stb_image_write, доступной здесь .Он реализует только несколько примитивов (и не обрабатывает ошибок), но тот же подход может быть расширен до того, что в основном является двоичным 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;
      }
   }
}

и вот как он записывает истинные цвета .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 голос
/ 14 ноября 2010

Если вы хотите десериализовать двоичные данные, одним из вариантов является определение макросов сериализации для структур, которые вы хотите использовать. Это lot проще в C ++ с шаблонными функциями и потоками. (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 или как угодно.

0 голосов
/ 14 ноября 2010

Вот код 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)
{
    /* ... */
}
0 голосов
/ 14 ноября 2010

Часть для чтения массива выглядит так, как будто она заслуживает своей собственной многократно используемой функции.Кроме того, если у вас действительно есть 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;

Конечно, вы можете продолжать использовать malloc и free вместо new[] и delete[], если данные передаются в код, который предполагает, что malloc был использован.

0 голосов
/ 14 ноября 2010

Я бы сделал ваш код менее неэлегичным, если бы немного реорганизовал его, чтобы ваши сложные структуры данных читались с помощью серии вызовов базовых типов.

Я предполагаю, что ваш код - чистый C, а не C ++, потому что в последнем вы, вероятно, будете генерировать исключения, а не использовать операторы goto.

0 голосов
/ 14 ноября 2010

Вас могут заинтересовать буферы протокола и другие схемы IDL.

...