【问题标题】:What is a good programming pattern for handling return values from stdio file writing functions什么是处理来自 stdio 文件写入函数的返回值的良好编程模式
【发布时间】:2009-02-20 13:32:55
【问题描述】:

我正在编写一些生成大量代码的代码

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

使用 g++ 编译时的警告,我想知道实际记录和处理大量单独顺序 fwrites 的返回值的最佳编程模式(即循环中的 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,因此需要基于 c 的解决方案来与代码库的其余部分保持一致。

【问题讨论】:

    标签: c stdio


    【解决方案1】:

    我会按照这些思路做一些事情:

    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() 返回写入的对象数!

    【讨论】:

    • 请注意,ferror() 告诉您流中是否存在错误。
    • 这种基于宏的方法的一个恼人问题是编译器错误不会报告with 语句中代码的正确行号——取决于编译器,它会报告包含with 关键字或带有任何错误的宏调用的右括号的行。
    【解决方案2】:

    穷人的基于 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】:

      你可以写一个包装函数

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

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

      【讨论】:

      • 我喜欢这个想法,但我想只保留它,所以没有例外。
      【解决方案4】:

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

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

      【讨论】:

      • 对,并且(正如您暗示的那样)将其与一系列测试结合使用,如果出现问题,每个测试都会返回错误。
      • @Sol:添加了更多明确性
      【解决方案5】:

      您可以像这样删除警告:

      (void) fwrite ( ,,,, );
      

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

      【讨论】:

        【解决方案6】:

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

        我曾经使用以下模式:

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

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

        【讨论】:

        • “单返回点”是一个非常的主观规则来编码。此处不应提及。
        • 我同意邦贝的观点。说明多重回报并不好,因为一般事实是不合理的。
        • 另外,fwrite 的返回值可能是非零并且仍然表示失败,您需要根据要写入的数据大小检查它
        • 单一返回点允许您只进行一次清理。我知道并不总是需要清理,但养成一个好习惯是有意义的。它得到了回报。
        • @David Dean - 这只是一个说明这个概念的例子。以这种方式进行正确的错误处理非常容易。
        【解决方案7】:

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

        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】:

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

          要让 GCC 安静,只需这样做:

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

          【讨论】:

            【解决方案9】:

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

            【讨论】:

              【解决方案10】:

              也许是这样的?您可以在不使代码难以阅读的情况下捕获错误,并且可以在人造循环结束后进行清理。

              #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】:

                这样的事情会起作用

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

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

                如果你不想使用智能指针,那么这会起作用,但它会很乱,所以我会先尝试智能指针路由

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

                【讨论】:

                  【解决方案12】:

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

                  【讨论】:

                  • 唯一的问题是 .. do other stuff .. 仍在执行,此时程序应该真正终止(因为其他东西基本上只是为了执行下一个 fwrite 而处理)
                  • 你是对的 - 我已经更新了代码并在答案中添加了一点警告。
                  • 你可以写 "if ((ok = ok && (fwrite(...)))) {" 来避免 'if' 块,如果你想要更多可读的代码,可以用宏包裹。
                  【解决方案13】:

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

                  【讨论】:

                  • 退出错误有点硬核,但如果它符合您的应用程序的需求,为什么不呢。但是,在这种情况下,无需担心清理,因此这并不能真正回答您最初的问题,并且包装只会转移负担。有关广泛使用的“最佳”实践,请参阅我的答案。
                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2018-09-11
                  • 1970-01-01
                  • 2020-05-03
                  • 2023-03-25
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多