Can I count the number of arguments to scanf?

Viewed 410

As we know, it's a good idea to check scanf for errors like this:

if(scanf("%d %d %d", &x, &y, &z) != 3) {
    /* Handle error */
}

But I wonder if there is any way to automatically detect that it should be 3.

One approach I have thought about is to declare the format string separately and then parse it. Something like this:

const char format[] = "%d %d %d";
size_t n = count(format);
if(scanf(format, &x, &y, &z) != n) {

But I have no idea how to implement count properly. I could do something like counting number of % but that would be very error prone. If there's no library function for this, I suspect it would be incredibly hard to get it right.

Another approach I have considered is doing something like this:

void wrapper(const char *format, ...) {
    va_list arg;
    va_start(arg, format);
    size_t n = count(arg);
    int done = __vfscanf_internal (stdin, format, arg, 0);
    if(done != n) {
    

But as far as I can see, there's no way to write the function count here. The specs of va_arg says: "If va_arg is called when there are no more arguments in ap, the behavior is undefined." so I cannot loop it until NULL or something like that.

A third approach is to see if scanf supports writing this number to a variable like this:

int n = scanf("%<somespecifier>%d %d %d", %c, %x, %y, %z);
if(n != c) {
    /* Handle error */
}

But that does not seem to be an option.

So I am at a loss here. Is there any way to do what I want?

My ultimate goal with this is to write a "safe" (I know that's a relative term when it comes to this) version of scanf that exits on failure. Something like

void safe_scanf(const char *format, ...) {
    /* Code */
    if(<wrong number of assignments>) {
        perror("Wrong number of assignments");
        exit(EXIT_FAILURE);
2 Answers

Did you consider a macro? They support variable arguments in a way that sometimes make them more useful than regular functions.

The code below is not tested, but can maybe get you started.

#define safe_sscanf(fmt, ...) {\
const char *p = fmt-1; int n=0;\
while (p=strstr(p+1, "%")) ++n;\
p = fmt-2;\
while (p=strstr(p+2, "%%")) n-=2;\
if (sscanf(fmt, __VA_ARGS__) != std::max(0,n)) exit(1);\
}

You can use this variadic macro trick to count the number of arguments passed to a function:

#define VA_NUM_ARGS(...) VA_NUM_ARGS_IMPL(__VA_ARGS__, 5,4,3,2,1)
#define VA_NUM_ARGS_IMPL(_1,_2,_3,_4,_5,N,...) N

This implementation works up to 5 arguments, but can be easily extended. So, you can wrap scanf into a wrapper like:

#define scanf_checked(...) scanf(__VA_ARGS__) - VA_NUM_ARGS(__VA_ARGS__) + 1

with the trailing + 1 needed to remove the format non-variadic argument from the count.

Assuming that you are passing the correct number of arguments (most compilers have an appropriate warning for it), you have to check if the return value is zero. This example uses a sscanf_checked, with + 2 because there is one more non-variadic argument:

#include <stdlib.h>
#include <stdio.h>
#include <assert.h>

#define VA_NUM_ARGS(...) VA_NUM_ARGS_IMPL(__VA_ARGS__, 5,4,3,2,1)
#define VA_NUM_ARGS_IMPL(_1,_2,_3,_4,_5,N,...) N

#define scanf_checked(...) scanf(__VA_ARGS__) - VA_NUM_ARGS(__VA_ARGS__) + 1
#define sscanf_checked(...) sscanf(__VA_ARGS__) - VA_NUM_ARGS(__VA_ARGS__) + 2

int main() {

    unsigned u1, u2;

    int i;

    i = sscanf_checked("1 2", "%u %u", &u1, &u2);
    assert(i == 0);

    i = sscanf_checked("1", "%u %u", &u1, &u2);
    assert(i != 0);

    i = sscanf_checked("1", "%u %u", &u1); // note: UB
    assert(i == 0); // assert may succeed, but compiler warns for too few arguments

    return EXIT_SUCCESS;

}

Working example here.

Related