Reputation: 192
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
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
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