Что такое хороший шаблон программирования для обработки возвращаемых значений из функций записи файла stdio - PullRequest
8 голосов
/ 20 февраля 2009

Я работаю над кодом, который генерирует много

ignoring return value of ‘size_t fwrite(const void*, size_t, size_t, FILE*)’, declared with attribute warn_unused_result

предупреждения при компиляции с g ++, и я задаюсь вопросом о лучшем шаблоне программирования для фактической записи и обработки возвращаемого значения большого числа отдельных последовательных fwrite с (т. Е. Не одинаковых fwrite в цикле)

Допустим, код выглядит так:

fwrite (&blah, sizeof (blah), 1, fp);
// ... more code ...
fwrite (&foo, sizeof (foo), 1, fp);
// ... more code ...

В настоящее время я думаю о чем-то подобном, но у меня могут возникнуть проблемы с очисткой указателя файла:

if (fwrite (&blah, sizeof (blah), 1, fp) != 1) return someerrorcode;
// ... more code ...
if (fwrite (&foo, sizeof (foo), 1, fp) != 1) return someerrorcode;
// ... more code ...

Я думаю, что этот подход явно лучше, чем вложение, которое слишком быстро сошло бы с ума:

if (fwrite (&blah, sizeof (blah), 1, fp) == 1) {
   // ... more code ...
   if (fwrite (&foo, sizeof (foo), 1, fp) == 1) {;
      // ... more code ...
   }
}

Конечно, уже есть установленный образец наилучшей практики для такого рода вещей, хотя?

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

dummy = fwrite (&blah, sizeof (blah), 1, fp);
// ... more code ...
dummy = fwrite (&foo, sizeof (foo), 1, fp);
// ... more code ...

Обновление: Я удалил тег c ++, поскольку этот код на самом деле просто компилируется с использованием g ++, поэтому необходимы решения на основе c, чтобы соответствовать остальной части кода.

Ответы [ 13 ]

10 голосов
/ 20 февраля 2009

Обработка исключений C для бедного человека на основе goto (фактически, единственный и единственный случай, когда goto НЕ вреден):

int foo() {
    FILE * fp = fopen(...);
    ....

    /* Note: fwrite returns the number of elements written, not bytes! */
    if (fwrite (&blah, sizeof (blah), 1, fp) != 1) goto error1;

    ...

    if (fwrite (&foo, sizeof (foo), 1, fp) != 1) goto error2;

    ...

ok:
    /* Everything went fine */
    fclose(fp);
    return 0;

error1:
    /* Error case 1 */
    fclose(fp);
    return -1;

error2:
    /* Error case 2 */
    fclose(fp);
    return -2;
}

Вы поняли идею. Реструктурируйте по своему усмотрению (одиночный / множественный возврат, одиночная очистка, пользовательские сообщения об ошибках и т. Д.). Исходя из моего опыта, это наиболее распространенный шаблон обработки ошибок Си. Важный момент: НИКОГДА, НИКОГДА не игнорируйте коды возврата stdlib, и любая веская причина для этого (например, читаемость) недостаточно хороша.

10 голосов
/ 20 февраля 2009

Я бы сделал что-то вроде этого:

FILE * file = fopen("foo", "wb");
if(!file) return FAILURE;

// assume failure by default
_Bool success = 0;

do
{
    if(!fwrite(&bar, sizeof(bar), 1, file))
        break;

    // [...]

    if(!fwrite(&baz, sizeof(baz), 1, file))
        break;

    // [...]

    success = 1;
} while(0);

fclose(file);

return success ? SUCCESS : FAILURE;

С небольшой магией макроса C99

#define with(SUBJECT, FINALIZE, ...) do { \
    if(SUBJECT) do { __VA_ARGS__ } while(0); if(SUBJECT) FINALIZE; \
} while(0)

и использование ferror() вместо нашего собственного флага ошибки, предложенного Джонатаном Леффлером, это можно записать как

FILE * file = fopen("foo", "wb");
with(file, fclose(file),
{
    if(!fwrite(&bar, sizeof(bar), 1, file))
        break;

    // [...]

    if(!fwrite(&baz, sizeof(baz), 1, file))
        break;

    // [...]
});

return file && !ferror(file) ? SUCCESS : FAILURE;

Если есть и другие ошибки, кроме ошибок io, вам все равно придется отслеживать их с помощью одной или нескольких переменных ошибок.

Кроме того, проверка по sizeof(blah) неверна: fwrite() возвращает количество написанных объектов!

2 голосов
/ 20 февраля 2009

Игнорирование ошибок - плохая идея. Гораздо лучше сделать что-нибудь неприятное, например, аварийно завершить работу программы, чтобы, по крайней мере, вы знали, что что-то пошло не так, а не молча. Еще лучше хорошая проверка ошибок и восстановление.

Если вы используете C ++, вы можете создать оболочку RAII для ФАЙЛА *, чтобы она всегда закрывалась. Посмотрите на std :: auto_ptr для идей. Затем вы можете вернуть полезный код ошибки или из функции, когда захотите, или вызвать исключение, и вам не придется беспокоиться о забытых элементах очистки.

2 голосов
/ 20 февраля 2009

Вы можете написать функцию-оболочку

void new_fwrite(a, b, c, d) {
    if (fwrite (a, b, c, b) != b) 
       throw exception;
}

и затем замените все вызовы fwrite на new_fwrite

0 голосов
/ 20 февраля 2009

Потенциально элегантное решение C для этого может быть примерно таким (предупреждение - непроверенный, не скомпилированный код впереди):


  size_t written;
  int    ok = 1;
  size_t num_elements = x;
  ok = (fwrite(stuff, sizeof(data), num_elements, outfile) == num_elements);

  if (ok) {
    ... do other stuff ...
  }

  ok = ok && (fwrite(stuff, sizeof(data), num_elements, outfile) == num_elements);

  if (ok) {
    ... etc etc ad nauseam ...
  }

  fclose(outfile);

  return ok;

Вышеуказанные две цели выполняются одновременно:

  • Возвращаемые значения проверяются, что исключает предупреждение и дает возможность вернуть код состояния.
  • Благодаря оценке короткого замыкания, если один из вызовов fwrite () завершится неудачно, последующие не будут выполнены, поэтому, по крайней мере, запись в файл останавливается, вместо того, чтобы дать вам потенциально поврежденный файл, если условие ошибки исчезает в середине функция, и вы можете снова записать данные

К сожалению, «уродливые» блоки if (ok) необходимы, если вы не хотите везде использовать оценку короткого замыкания. Я видел эту схему, которая использовалась в сравнительно небольших функциях, использующих оценку короткого замыкания, везде, и я думаю, что она, вероятно, лучше всего подходит для этого конкретного использования.

0 голосов
/ 20 февраля 2009

Хорошо, учитывая, что я ищу решение c (без исключений), как насчет:

void safe_fwrite(data,size,count,fp) {
   if (fwrite(data,size,count,fp) != count) {
      printf("[ERROR] fwrite failed!\n");
      fclose(fp);
      exit(4);
   }
}

И тогда в моем коде есть:

safe_fwrite (&blah, sizeof (blah), 1, fp);
// ... more code ...
safe_fwrite (&foo, sizeof (foo), 1, fp);
// ... more code ...
0 голосов
/ 20 февраля 2009

Может как то так? Вы ловите ошибки, не делая код слишком нечитаемым, и вы можете выполнить очистку после окончания цикла faux.

#define WRITE_ERROR 100
#define WRITE_OK 0

int do_fwrite(void* ptr, size_t bytes, int fp) {
  if ( fwrite(ptr, bytes, 1, fp) != bytes ) return WRITE_ERROR;
  return WRITE_OK;
}

int my_func() {
   int errcode = 0;

   ...
   do {
     if ( errcode = do_fwrite(&blah, sizeof(blah), fp) ) break;
     ....
     if ( errcode = do_fwrite(&foo, sizeof(foo), fp) ) break;
     ....
     etc
   } while( false );

   fclose(fp);
   return errcode;
}
0 голосов
/ 20 февраля 2009

Почему бы вам не обернуть fwrite в какой-либо объект Writer и не выдать исключение, если fwrite () возвращает код ошибки? Легко кодировать, легко использовать, легко управлять. ИМХО, конечно. :)

0 голосов
/ 20 февраля 2009

Примерно так будет работать

if (fwrite (&blah, sizeof (blah), 1, fp) != 1) throw SomeException;

Если вы беспокоитесь о чистоте указателей, вы можете обернуть указатель в какую-то форму умного указателя перед выполнением fwrite.

Если вы не хотите использовать умные указатели, тогда это будет работать, но это грязно, поэтому я сначала попробую маршрут умных указателей

if (fwrite (&blah, sizeof (blah), 1, fp) != 1) {
    //cleanup here
    throw SomeException;
}
0 голосов
/ 20 февраля 2009

Ваше первое решение выглядит хорошо. Обычно goto err; оказывается более удобным, так как вам может потребоваться некоторая общая часть очистки (например, перемотка в известную позицию).

Чтобы сделать GCC тихим, просто сделайте:

(void)fwrite (&blah, sizeof (blah), 1, fp);
(void)fwrite (&foo, sizeof (foo), 1, fp);
...