代码之家  ›  专栏  ›  技术社区  ›  David Dean

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

  •  8
  • David Dean  · 技术社区  · 15 年前

    我正在研究一些生成大量

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

    使用g++编译时出现警告,我想知道实际记录和处理大量单独序列的返回值的最佳编程模式。 fwrite S(即不相同 写入文件 在循环中)

    假设代码现在看起来是这样的:

    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,所以需要基于C的解决方案来保持代码库的其余部分。

    13 回复  |  直到 10 年前
        1
  •  10
  •   Christoph    15 年前

    我会沿着这条线做一些事情:

    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
  •  10
  •   fbonnet    15 年前

    穷人基于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返回代码,任何这样做的好理由(例如可读性)都不够好。

        3
  •  2
  •   Patrick McDonald    15 年前

    您可以编写一个包装函数

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

    然后用新的“写入”替换所有对“写入”的调用

        4
  •  2
  •   Mr Fooz    15 年前

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

    如果你使用C++,你可以为文件创建一个RAII包装器,以便它总是被关闭。查看std::auto_ptr了解想法。然后,您可以随时返回一个有用的错误代码或函数,或者抛出一个异常,而不必担心忘记的清理项。

        5
  •  0
  •   anon    15 年前

    您可以删除如下警告:

    (void) fwrite ( ,,,, );
    

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

        6
  •  0
  •   qrdl    15 年前

    嵌套不好,多次返回也不好。

    我以前用过以下模式:

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

    我知道它看起来很难看,但它只有一个返回点,没有过度的嵌套和非常容易维护。

        7
  •  0
  •   unwind    15 年前

    好。。。您可以创建一个包装函数,该函数在写入失败时重新尝试写入,可能最多重试几次,并返回success/failure:

    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");
    
        8
  •  0
  •   kmkaplan    15 年前

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

    要使GCC安静,只需:

    (void)fwrite (&blah, sizeof (blah), 1, fp);
    (void)fwrite (&foo, sizeof (foo), 1, fp);
    
        9
  •  0
  •   Bombe    15 年前

    为什么不把 fwrite 如果fwrite()返回错误代码,会将异常抛出到某种类型的编写器对象中吗?易于编码,易于使用,易于管理。当然了。:)

        10
  •  0
  •   Eric Petroelje    15 年前

    也许是这样?您可以捕获错误,而不会使代码太不可读,并且可以在人造循环结束后进行清理。

    #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
  •   Glen    15 年前

    像这样的东西行得通

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

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

    如果你不想使用智能指针,那么这就可以了,但这很麻烦,所以我先试试智能指针路由

    if (fwrite (&blah, sizeof (blah), 1, fp) != 1) {
        //cleanup here
        throw SomeException;
    }
    
        12
  •  0
  •   Timo Geusch    15 年前

    对于这一点,一个潜在的优雅的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) 如果您不想在任何地方使用短路评估,则必须使用块。我已经看到这种模式在使用短路评估的相对较小的函数中随处可见,我认为它可能最适合这种特定的使用。

        13
  •  0
  •   Thomas Eding    10 年前

    好吧,既然我在找 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 ...