从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的解决方案来保持代码库的其余部分。

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

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

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

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

你可以写一个包装函数

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

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

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

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

你可以删除这样的警告:

 (void) fwrite ( ,,,, ); 

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

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

我曾经使用以下模式:

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

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

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

 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"); 

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

为了使GCC安静,只需:

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

为什么不将fwrite包装成某种类型的Writer对象并在fwrite()返回错误代码时抛出exception? 易于编码,易于使用,易于管理。 恕我直言,当然。 🙂

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

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

像这样的东西会起作用

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

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

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

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

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

好吧,鉴于我正在寻找一个解决方案(没有例外),如何:

 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 ...