【问题标题】:Check scanf(..) success without using the number of fields — is there a way?在不使用字段数量的情况下检查 scanf(..) 是否成功——有没有办法?
【发布时间】:2015-08-13 06:50:33
【问题描述】:

一个典型的成语是:

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(..)),最后一个- 通过将errno 设置为EILSEQ。但我对包罗万象(即包括确定是否发生匹配失败)感兴趣。

附:看看上面简洁的断章取义的例子,这似乎要求太多,但考虑一下实际实践,你可以快速做出许多改变,并且要记住数百个这样的小事情,每次你改变一件事,你必须在别处改变另一件事。然后很明显,养成消除各种依赖的习惯是有回报的。

【问题讨论】:

  • 不要假设feof 可以返回“true”而ferror 返回“false”,反之亦然。并且不要检查errno,除非最后一个函数调用实际上失败并且被指定为实际设置errnoscanf and family 不是)。如果将errno 设置为失败的函数没有失败,则errno 的值未指定。
  • 否;除了检查返回的数字是否是预期的数字之外,没有可靠的方法来确定scanf() 家庭例程是否有效。当你做出改变时,你必须思考。即使您进行了数百次更改,您也必须考虑每一项,并确保您做出了正确的更改。欢迎来到编程的世界——对细节的偏执关注是你获得高薪的原因。顺便说一句,如果您使用 GCC 并设置了足够多的警告选项,系统会告诉您是否与格式字符串和其他参数不匹配,但它不会告诉您进行了错误的比较。
  • @JonathanLeffler 我正在等待欢迎你进入编程世界的那一天,在那里你会因为好的想法而获得报酬。

标签: c scanf


【解决方案1】:

你可以这样做:

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 是一个函数,它读取字符串以查看其中有多少%(将%% 计数为零)。

【讨论】:

  • 也忽略%*,以及[] 转换说明符中的任何%。丢弃scanf 并使用更规范的东西可能更容易。
  • @n.m.没有争论!
  • 也可以忽略%n,但谁知道呢。 C 标准说:“执行 %n 指令不会增加执行完成时返回的赋值计数”,但勘误表似乎与此相矛盾(Linux 手册页)。
【解决方案2】:

你可以这样做:

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

【讨论】:

  • 那是未定义的行为。如果您关闭-Wformat,它在实践中主要是有效的,但无论如何都要避免一些事情。
  • @mtijanic 为什么会这样?我没有看到任何关于 -Wformat 的警告。
  • 我的错,我看错了你的格式。抱歉,是的,效果很好。
【解决方案3】:

就像很多事情一样,这可以通过一点预处理器的魔法来完成。首先,我们需要一个宏来计算参数的数量。让我们以@JamesMcNellis 的this one 为例

// 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)

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

现在我们包装 scanf 以在成功时返回 true,在失败时返回 false:

#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 的返回值。是的,不要那样做。它可能看起来更好,但如果其他人查看您的代码,他们不会知道您更改了它,并且看起来使用不当。

【讨论】:

  • causes undefined behaviour#define是一个保留字或者标准库函数的名字
  • @MattMcNabb 是的,之所以引入,是因为允许实现将函数实现为宏。所以,如果scanf 已经是一个宏,它会编译失败(你需要#undef 它)。此外,其他一些函数可能是使用scanf 的宏,如果您已经重新定义了它,这将是无效的。标准将其称为 UB 比指定可以或不能使用它的所有情况更容易。不过,不要那样做
  • 另一个用途是#define malloc(x) malloc_tracked(x),它适用于所有真正的编译器/libc 组合,即使在技术上是 UB。
  • %n 让事情变得不那么整洁(再次!)
  • 我认为这很棒。一个可能的问题是,如果我们有超过 4+1 个参数传递给scanf_return_bool(..),则错误是error: ISO C++ forbids comparison between pointer and integer [-fpermissive],这可能会造成混淆。 (发生这种情况是因为上述宏中的 N 落在 scanf_return_bool(..)&a 参数之一上。)易于修复:只需按照建议扩展为 32 个参数。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-02-01
  • 2019-06-17
  • 2023-03-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-29
相关资源
最近更新 更多