Leonhart231
Leonhart231

Reputation: 192

No error returned in my printf wrapper using varargs

I've recently gotten back into working with C, and I decided to write a library as a wrapper for stdio.h. The goal is to do all the error checking possible so that the user won't have to do it themselves whenever they call a stdio function. This is partly for learning, and partly for real use (since I frequently use stdio).

When I write the following (in main), gcc gives an error at compile time, since there is supposed to be an integer as another argument but none was passed.

printf("Your integer: %d\n");

In case it's useful, here are my compiler flags:

-std=c11 -pedantic-errors -Wall -Wextra -Werror

Here's part of my current wrapper function. It works perfectly and checks for quite a few errors when passed valid/correct arguments:

uintmax_t gsh_printf(const char *format, ...)
{
    va_list arg;
    int cnt;
    va_start(arg, format);
    cnt = vprintf(format, arg);
    va_end(arg);
    // Analyze cnt and check for stream errors here
    return (uintmax_t)cnt;
}

But here's the problem, if I call:

gsh_printf("Your integer: %d\n");

It does not give an error, and it even runs! The usual output is something like:

Your integer: 1799713

Or some other number, implying that it's accessing memory not allocated to it, but it never gives a segmentation fault either.

So, why does it not give an error of any kind? And how can I write my own code so that there is a compile-time error, or at least run-time error after checking types, number of args, etc.?

Of course, any help is greatly appreciated, and if you need any more information, just let me know. Thank you!

Upvotes: 2

Views: 344

Answers (2)

ouah
ouah

Reputation: 145899

With fprintf and fscanf families of functions, if a conversion specification corresponding argument is missing, the function call invokes undefined behavior.

With gcc use format (archetype, string-index, first-to-check) function attribute to request a diagnostic:

extern uintmax_t gsh_printf(const char *format, ...)
    __attribute__ ((format (printf, 1, 2)));

uintmax_t gsh_printf(const char *format, ...)
{
    va_list arg;
    int cnt;
    va_start(arg, format);
    cnt = vprintf(format, arg);
    va_end(arg);
    // Analyze cnt and check for stream errors here
    return (uintmax_t)cnt;
}

See documentation for an explanation of archetype, string-index and first-to-check:

http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html

For example with the example above, with -Wall (because of -Wformat), with this statement:

gsh_printf("Your integer: %d\n");

you'll get this warning:

warning: format ‘%d’ expects a matching ‘int’ argument [-Wformat]

With -Wall (because of -Wformat-extra-args) you will also get a warning for extra arguments:

gsh_printf("Your integer: %d\n", 0, 1);

gives

warning: too many arguments for format [-Wformat-extra-args]

Upvotes: 4

cementblocks
cementblocks

Reputation: 4616

In C there is no way to determine whether or not the number of arguments that are passed when using va_list is the amount of arguments required. In the C calling convention the arguments are pushed onto the stack starting from the right-most argument. The way printf works is it parses the format string and pops a value from the stack whenever needed. Thus you get the random number when calling

gsh_printf("Your integer: %d\n");

You would need to know in advance how many arguments are supplied which cannot be done using va_list.

You might be able to get around this by using some kind of container class to hold all the arguments and use the number of elements in the container to check if there are enough.

Also notice that 'args' is just a pointer to the start of the argument list. So when you pass it to vprintf, vprintf just prints the value of the pointer.

Upvotes: 0

Related Questions