从stdio文件写入函数处理返回值有什么好的编程模式

时间:2009-02-20 13:32:56

标签: c stdio

我正在研究一些产生大量

的代码
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 个答案:

答案 0 :(得分:10)

基于goto的穷人的C异常处理(事实上,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;
}

你明白了。根据需要进行重组(单次/多次返回,单次清理,自定义错误消息等)。根据我的经验,这是最常见的C错误处理模式。关键点在于:永远不要忽略stdlib返回码,任何有理由这样做(例如可读性)都不够好。

答案 1 :(得分:10)

我会沿着这些方向做点什么:

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()代替Jonathan Leffler建议的错误标记,可以写成

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 :(得分:2)

您可以编写包装函数

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

然后用new_fwrite替换所有对fwrite的调用

答案 3 :(得分:2)

忽略错误是一个坏主意。做一些令人讨厌的事情会更好,比如让程序崩溃,这样至少你知道出了什么问题,而不是默默地继续进行。更好的是错误检查和恢复。

如果你正在使用C ++,你可以为FILE *创建一个RAII包装器,这样它就会一直关闭。看看std :: auto_ptr的想法。然后,您可以随时返回有用的错误代码或函数,或者抛出异常而不必担心忘记清理项目。

答案 4 :(得分:0)

您可以删除以下警告:

(void) fwrite ( ,,,, );

解决你的主要问题,如果任何fwrite()调用失败,我猜它继续没有意义,因为输出可能是损坏的。在这种情况下,当你标记这个C ++时,我会抛出异常。

答案 5 :(得分:0)

嵌套很糟糕,多次返回也不好。

我以前使用以下模式:

#define SUCCESS (0)
#define FAIL    (-1)
int ret = SUCCESS;

if (!fwrite(...))
    ret = FAIL;
if (SUCCESS == ret) {
    do_something;
    do_something_more;
    if (!fwrite(...))
        ret = FAIL;
}
if (SUCCESS == ret)
    do_something;

return ret;

我知道它看起来很丑,但它有单一的返回点,没有过多的嵌套,很容易维护。

答案 6 :(得分:0)

嗯......您可以创建一个包装函数,如果失败则重新尝试写入,可能达到最大重试次数,并返回成功/失败:

int safe_fwrite(FILE *file, const void *data, size_t nbytes, unsigned int retries);
void print_and_exit(const char *message);

然后您的主要代码可以写成

#define RETRIES 5
if(!safe_fwrite(fp, &blah, sizeof blah, RETRIES))
  print_and_exit("Blah writing failed, aborting");
if(!safe_fwrite(fp, &foo, sizeof foo, RETRIES))
  print_and_exit("Foo writing failed, aborting");

答案 7 :(得分:0)

您的第一个解决方案看起来不错。通常goto err;更方便,因为您可能需要一些常见的清理部分(例如倒带到已知位置)。

让GCC保持安静只做:

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

答案 8 :(得分:0)

这样的事情会起作用

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

如果你担心指针被清理,你可以在执行你的fwrite之前将指针包裹在某种形式的智能指针中。

如果您不想使用智能指针,那么这将有效,但它很麻烦,所以我先尝试智能指针路径

if (fwrite (&blah, sizeof (blah), 1, fp) != 1) {
    //cleanup here
    throw SomeException;
}

答案 9 :(得分:0)

为什么不将fwrite包装到某种Writer对象中并在fwrite()返回错误代码时抛出异常?易于编码,易于使用,易于管理。恕我直言,当然。 :)

答案 10 :(得分:0)

也许是这样的?你捕获错误而不会使代码太难以理解,你可以在假循环结束后进行清理。

#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;
}

答案 11 :(得分:0)

好的,鉴于我正在寻找一个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 ...

答案 12 :(得分:0)

一个可能优雅的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)块是必要的。我已经看到这种模式在相对较小的函数中使用到处使用短路评估,我认为它可能最适合于特定用途。