Welcome to Software Development on Codidact!
Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.
Is it OK to use scanf with a void pointer?
I've created a function that calls scanf
passing a void pointer as argument:
void read(const char *format, void *p) {
scanf(format, p);
}
And tested it with different types:
int n;
read("%d", &n);
printf("read int: %d\n", n);
float f;
read("%f", &f);
printf("read float: %f\n", f);
char s[100];
read("%s", s);
printf("read string: %s\n", s);
I've made these tests in Ubuntu 20.04.3, with clang 10.0.0-4ubuntu1 and gcc 9.3.0. Both were compiled with the options -std=c11 -pedantic-errors -Wall -Wextra -Werror
, without any errors/warnings, and both worked fine (all data were correctly read and printed).
But "it worked" doesn't necessarily mean it's correct (specially in C), hence the question: is scanf
supposed to work like that (receiving a void pointer and correctly assigning the data to it, according to the format specifier), or is it a big coincidence and should be avoided? Are there any cases that could fail (excluding the obvious ones, such as the format doesn't match the second argument)?
PS: The point here is not to discuss the merits of the read
function (if it's useless, or should check the return value of scanf
, or should use va_list
/vfscanf
instead, etc). I'm just interested in knowing if this behaviour of scanf
(accepting a void pointer and correctly "guessing" its type based on the specifier) is expected (defined by the standard, for instance). In case it's not, an explanation about why it worked is also appreciated.
2 answers
Void pointers are compatible with every other object pointer type and as mentioned in another answer, 7.21.6/10 speaks of the type of the pointed at object, not the type of the pointer. This is consistent with the rules of effective type/pointer aliasing (6.5/6), which have to be applied as well, since the passed pointer does not necessarily point to a chunk of memory with an object of a declared type (could as well be a void pointer returned from malloc
). scanf
has to be regarded as a "lvalue" access following the rules of effective type. Examples:
float f;
scanf("%d", &f); // undefined behavior, 7.21.6/10 and 6.5/7
void* v = &f;
scanf("%f", v); // well-defined behavior, object f has declared and effective type float
void* p = malloc(n); // location pointed at by p has no declared type
scanf("%d", p); // well-defined, *p is now to be regarded as effective type int
For scanf
to make any sense, it will have to internally cast the passed pointer to a pointer to the type of the specified conversion specifier. Any type information that the passed pointers might have had is lost through the varadic function/va_list
parameter passing anyway.
Notably, there's a whole lot of scenarios where scanf
can go wrong, so it is mostly to be used for debugging purposes - it is not a function recommended to be used in any professional release. If your program for some reason must use console input, then use fgets
as far as possible.
1 comment thread
From section 7.21.6.2 of this draft:
[T]he result of the conversion is placed in the object pointed to by the first argument following the
format
argument that has not already received a conversion result. If this object does not have an appropriate type, or if the result of the conversion cannot be represented in the object, the behavior is undefined.
So per the spec, it is the type of the object, not the pointer to the object, that must match the format specifier. A compiler that doesn't interpret the quoted code to have the behavior you saw would on this point not be spec-compliant.
0 comment threads