2015-08-13 16 views
2

典型成语是:检查scanf(..)成功,而不使用字段数 - 有没有办法?

if (scanf("%d%d%d", &a, &b &c) != 3) 
    handle_the_failure(); 

3,场的数量,是多余的。如果我将模式和参数更改为scanf(..),但忘记更改3,那是另一个编译 - 测试 - 调试周期,这是浪费时间。

有没有一种成语可以检查scanf(..)的(绝对)成功,而无需在代码中写入字段数?也许是这样的:

scanf("%d%d%d", &a, &b &c); 
if (lastScanfFailedInAnyWay()) 
    handle_the_failure(); 

documentation(见 “返回值” 一节)谈论四种不同的情况:

  1. 档案结尾

  2. 读取错误

  3. 配对失败

  4. 个编码错误解读宽字符

前两个是由feof(..)ferror(..)(我假设feof(..)意味着ferror(..)),最后解决 - 通过设置errnoEILSEQ。但是我对一个全面的(即包括确定是否发生匹配失败)感兴趣。

P.S.看看上面这个完整的不在上下文中的例子,这看起来好像太多了,但是考虑一下真正的练习,在这里你可以快速做出很多变化,并且有数百个这样的小东西需要记住,每一次你改变了一件事,你必须改变另一件事。那么很明显,消除各种依赖关系的发展习惯是有益的。

+4

不要假设['feof'](http://en.cppreference.com/w/c/io/feof)可能会在['ferror'](http:// en。 cppreference.com/w/c/io/ferror)会返回“false”,反之亦然。除非最后的函数调用实际上失败,并且指定实际设置'errno'(['scanf'和family](http://en.cppreference.com/w/c/io/) fscanf)不是)。如果一个在失败时设置'errno'的函数没有失败,那么'errno'的值是未指定的。 –

+3

否;除了检查返回的数字是否是预期的数字外,没有一种可靠的方法可以确定'scanf()'家族例程是否工作。当你做出改变时你必须考虑。即使您进行了数百次更改,您也必须考虑每项更改,并确保您做出了正确的更改。欢迎来到编程世界 - 关注细节就是为什么你能得到好的收益。顺便提一下,如果你在设置了足够的警告选项的情况下使用GCC,你会被告知是否你不匹配格式字符串和其他参数,但它不会告诉你做出了错误的比较。 –

+1

@JonathanLeffler我在等待那天能够欢迎你进入编程世界的日子,而你却因为好主意而获得报酬。 –

回答

4

你可以做这样的事情:

bool wrap_the_scanf(char const *fmt, ...) 
{ 
    va_list ap; 
    va_start(ap, fmt); 
    int result = vscanf(fmt, ap); 
    va_end(ap); 
    return result == count_specifiers(fmt); 
} 

其中count_specifiers是读取字符串看到许多%如何在它(计%%为零)的功能。

+2

也忽略''*''转换说明符中的'%*'和任何'%'。丢弃''scanf''并且使用一些更精确的规范可能会更容易。 –

+0

@ n.m。没有论据! –

+1

也可能不理睬'%n',但谁知道。 * C标准说:“执行%n指令不会增加在执行完成时返回的赋值计数”,但更正似乎与*(Linux手册页)相矛盾。 –

3

你可以做这样的事情:

int nch = -1; 
scanf("%d%d%d%n", &a, &b, &c, &nch); 
if (nch == -1) 
    handle_the_failure(); 
+0

这是未定义的行为。如果你关闭了'-Wformat',那么它在实践中大部分都会工作,但是无论如何要避免。 – mtijanic

+0

@mtijanic这是为什么?我没有看到-Wformat的任何警告。 –

+0

我的不好,我误解了你的格式。对不起,对,那很好。 – mtijanic

2

像很多的东西,这可以用一点神奇的预处理器来完成。首先,我们需要一个宏来计算参数的数量。让我们通过this one @JamesMcNellis

// Expand up to 32 arguments if needed 
#define VA_NARGS_IMPL(_1, _2, _3, _4, _5, N, ...) N 
#define VA_NARGS(...) VA_NARGS_IMPL(__VA_ARGS__, 5, 4, 3, 2, 1) 

如果我们调用VA_NARGS所有scanf参数将返回字段数,加上一个格式说明。

现在我们换scanf函数对成功返回true,失败错误:

#define scanf_return_bool(...) (scanf(__VA_ARGS__) == VA_NARGS(__VA_ARGS__)-1) 

您可以使用此为:

if (!scanf_return_bool("%d %d", &a, &b)) 
    handle_error(); 

一个有趣的注意:您可以重命名scanf_return_bool只是scanf ,并且有效地覆盖了scanf的返回值。是的,不要这样做。看起来好一些,但如果别人查看你的代码,他们不会知道你改变了它,而且看起来好像是不正确的使用。

+0

它[导致未定义的行为](http://stackoverflow.com/questions/14770384/is-it-undefined-behavior-to-redefine-a-standard-name)到'#define'保留字或名称一个标准的库函数 –

+0

@MattMcNabb是的,这是因为允许实现将函数实现为宏而引入的。所以,如果'scanf'已经是一个宏,它将无法编译(你需要'#undef')。此外,其他一些函数可能是使用'scanf'的宏,如果您已经重新定义了它,这将是无效的。标准将它称为UB比指定所有可以使用或不可使用的情况更容易。不过,**不要那样做**。 – mtijanic

+0

另一个用法是'#define malloc(x)malloc_tracked(x)',它将适用于所有真正的编译器/ libc组合,即使技术上是UB。 – mtijanic

相关问题