2

A typical idiom is:

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

This 3, the number of fields, is redundant. If I change the pattern and the arguments to scanf(..), but forget to change the 3, that's another compile-test-debug cycle, which is a waste of time.

Is there an idiom that allows to check the (absolute) success of scanf(..) without having to write the number of fields in the code? Maybe something like this:

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

The documentation (see "Return value" section) talks about four different conditions:

  1. End-of-file

  2. Reading error

  3. Matching failure

  4. Encoding error interpreting wide characters

The first two are addressed by feof(..) and ferror(..) (I'm assuming feof(..) implies ferror(..)), and the last — by setting errno to EILSEQ. But I'm interested in a catch-all (i.e. including figuring out if a matching failure occurred).

P.S. Looking at the neat out-of-context example above, this may seem like too much to ask, but consider real practice, where you make many changes rapidly, and there are hundreds of little things like this to keep in mind, that every time you change one thing, you have to change another one elsewhere. Then it becomes clear that developing habits that eliminate dependencies of various sorts pays off.

Yu Hao
  • 119,891
  • 44
  • 235
  • 294
Evgeni Sergeev
  • 22,495
  • 17
  • 107
  • 124
  • 4
    Don't assume, [`feof`](http://en.cppreference.com/w/c/io/feof) could return "true" while [`ferror`](http://en.cppreference.com/w/c/io/ferror) returns "false", and the opposite too. And don't check `errno` unless the last function call actually failed and is specified to actually set `errno` (which [`scanf` and family](http://en.cppreference.com/w/c/io/fscanf) is not). If a function that sets `errno` on failure doesn't fail, then the value of `errno` is unspecified. – Some programmer dude Aug 13 '15 at 06:53
  • 3
    No; there isn't a reliable way to spot whether a `scanf()` family routine worked other than checking that the number returned is the number expected. You have to think when you make changes. Even when you make hundreds of changes, you have to think about each one, and make sure you have made the correct change. Welcome to the world of programming — paranoid attention to detail is why you get paid well. Incidentally, if you use GCC with enough warning options set, you'll be told if you mismatch the format string and the other arguments, but it'll not tell you're making the wrong comparison. – Jonathan Leffler Aug 13 '15 at 07:01
  • 1
    @JonathanLeffler I'm waiting for the day when I can welcome you to the world of programming where you get paid for good ideas instead. – Evgeni Sergeev Aug 13 '15 at 08:19

3 Answers3

4

You could do something like this:

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

where count_specifiers is a function that reads the string seeing how many % are in it (counting %% as zero).

M.M
  • 138,810
  • 21
  • 208
  • 365
  • 2
    Also disregard `%*`, and any `%` inside a `[]` conversion specifier. It could be easier to just discard`scanf` and use something with a saner spec. – n. m. could be an AI Aug 13 '15 at 07:37
  • @n.m. no arguments there! – M.M Aug 13 '15 at 07:43
  • 1
    Also maybe disregard `%n`, but who knows. *The C standard says: "Execution of a %n directive does not increment the assignment count returned at the completion of execution" but the Corrigendum seems to contradict this* (Linux man page). – n. m. could be an AI Aug 13 '15 at 07:44
3

You can do something like this:

int nch = -1;
scanf("%d%d%d%n", &a, &b, &c, &nch);
if (nch == -1)
  handle_the_failure();
n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
2

Like a lot of things, this can be done with a little preprocessor magic. First, we need a macro to count the number of arguments. Let's take this one by @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)

If we call VA_NARGS with all scanf argument it will return the number of fields, plus one for the format specifier.

Now we wrap scanf to return true on success, and false on failure:

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

You can use this as:

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

An interesting note: You can rename scanf_return_bool to just scanf, and effectively override the return value of scanf. Yeah, don't do that. It may seem better, but if someone else were to look over your code, they wouldn't that know you changed it, and it would seem like improper use.

Community
  • 1
  • 1
mtijanic
  • 2,872
  • 11
  • 26
  • It [causes undefined behaviour](http://stackoverflow.com/questions/14770384/is-it-undefined-behavior-to-redefine-a-standard-name) to `#define` a reserved word or the name of a standard library function – M.M Aug 13 '15 at 08:00
  • @MattMcNabb Yeah, that is introduced because the implementation is allowed to implement the functions as macros instead. So, if `scanf` is already a macro, it would fail compiling (you'd need to `#undef` it). Also, some other functions may be macros that use `scanf`, which would be invalid if you already redefine it. It's easier for the standard to just call it UB than specify all the cases in which it can or can't be used. Still, **don't do that**. – mtijanic Aug 13 '15 at 08:05
  • Another use would be `#define malloc(x) malloc_tracked(x)`, which will work on all real compiler/libc combos, even though technically UB. – mtijanic Aug 13 '15 at 08:06
  • 1
    `%n` makes things not so neat (again!) – n. m. could be an AI Aug 13 '15 at 08:18
  • I think that's great. A little possible problem though, is that if we have more than 4+1 arguments to `scanf_return_bool(..)` as above, the error is `error: ISO C++ forbids comparison between pointer and integer [-fpermissive]`, which can be confusing. (It happens because `N` in the above macro falls on one of the `&a` arguments to `scanf_return_bool(..)`.) Easy to fix: just expand to 32 arguments as suggested. – Evgeni Sergeev Aug 13 '15 at 08:33