What is the behavior of printf() when we supply multiple arguments to it without a format specifier?
Example:
int main()
{
printf("hello", "hi");
return 0;
}
Why does the compiler produce a warning on compilation of the above program? :
warning: too many arguments for format [-Wformat-extra-args]
If we compile the similar program below:
int main()
{
char *s1 = "hello";
char *s2 = "hi";
printf(s1, s2);
}
No warnings are produced. What is the reason for this?
Also, why do both programs output hello only, and don't also print hi?
The C 2018 standard specifies the behavior of printf in clause 7.21.6.3, in which paragraph 2 says “The printf function is equivalent to fprintf with the argument stdout interposed before the arguments to printf.”
The standard specifies the behavior of fprintf in 7.21.6.1, which tells us the second argument (the first argument of printf) is a format string and that it may contain various conversion specifications introduced by the character “%”. Thus, in printf("hello", "hi"), "hello" is a format string with no conversion specifications. In this case, paragraph 2 tells us what happens:
If the format is exhausted [fully processed] while arguments remain, the excess arguments are evaluated (as always) but are otherwise ignored.
Thus, in printf("hello", "hi"), "hi" is ignored, and "hello" is a format string that contains only ordinary characters, which are copied to the output stream per paragraph 3.
The compiler warns about printf("hello", "hi") because it is able to see that this call contains an excess argument because the format string does not contain a conversion specification for it.
Your compiler does not warn about printf(s1,s2); because it does not analyze what s1 will contain during this call. This sort of analysis is not impossible in this situation, but situations like this are rare: When a programmer uses a pointer to a string as the format string for printf, it is usually a string or pointer that is computed, constructed, or selected during program execution, and the manner of this computation is often beyond the ability of a compiler to analyze. Situations where the pointer is clearly a pointer to a fixed string are rare, since they are not frequently useful, so presumably compiler implementors have not found it valuable to implement the code necessary for the compiler to handle these situations.
tl;dr: Extra arguments to printf() are ignored.
The official C language standard (the link is to a draft of the C11 version) says the following:
§ 7.21.6.1 The fprintf function
...
... If the format is exhausted while arguments remain, the excess arguments are evaluated (as always) but are otherwise ignored. The fprintf function returns when the end of the format string is encountered.
... and printf() is simply fprintf() targeted at the standard output file.
About your two code snippets:
The compiler is giving you a hint, for the first snippet, that the number of arguments doesn't match the number of specifiers in the format string. It's just a courtesy - it's not required to notice this. This also explains why the compiler does not notice it for the second snippet. It could, but it's too much effort to chase your pointers and check what they point at.
In both cases, your format string is your first argument to printf(), i.e. "hello". That string has no format specifiers, so the printf() looks at the "hello", and understands it only needs to print that and doesn't need process any other arguments. That's whi it ignores "hi".
The first parameter of printf is the format string, because printf is about printing formatted data. To specify how to format the data, printf uses the first argument. This is different from other languages and libraries where all the parameters (like Python's print) are used in the same way and formatting is done through other means.
The first and second examples you provide are both "incorrect" although technically valid because you are passing a format string that does not need any extra argument, so "hi" is unused.
What you may want to do instead is:
printf("%s %s", "hello", "hi");
many compilers know well very well the printf function family and read compile time the format string analysing the parameters. printf("hello",s2); compiler see that there is no %... in the format string and does not expect any other parameters. Warning is issued
if you call printf(s1,s2); compiler does not know what is the content of the s1 and it cannot go through the format string and no warning issued.
Many compilers have special extension to inform them that your function is printf like and you want compiler to read the format string - gcc:
extern int
my_printf (void *my_object, const char *my_format, ...)
__attribute__ ((format (printf, 2, 3)));
Related
This question already has answers here:
Behaviour of printf when printing a %d without supplying variable name
(6 answers)
Closed 4 years ago.
I stumbled upon this C code in a college test and when I tested it on Dev-C++ 5.11 compiler, it printed random characters. I can't understand how or why this code works. Can someone enlighten me?
int main() {
char a[10] = "%s" ;
printf( a ) ;
}
This question has two parts: the missing quotes and the random characters.
printf() is just a function. You can pass strings and other values to functions as arguments. You don't have to use a literal. You can use both char *a = "something"; printf(a) (passing a variable as an argument) and printf("something") (passing a string literal as an argument).
printf() is also a variadic function. This means it can accept any number of arguments. You can use printf("hello world"), printf("%s", "hello world") and even printf("%s %s", "hello", "world"). Some older compilers don't verify you actually passed the right number of arguments based on the first argument which is the format string. This is why your code compiles even though it's missing an argument. When the program runs the code goes over the format string, sees "%s" and looks for the second argument to print it as a string. Since there is no second argument it basically reads random memory and you get garbage characters.
printf function signature is:
int printf(const char *format, ...);
It expects format string as the first argument and variable number of arguments that are handled and printed based on the format specifiers in the format string. variable a in your question is providing it the format string. Reason for random characters is that the argument for format specifier %s is missing. Following will correctly print a string:
printf( a, "Hello World!" );
A list of format specifiers can be seen here https://en.wikipedia.org/wiki/Printf_format_string
Why does it compile?
Because variadic arguments accepted by printf are processed at run time. Not all compilers do compile time checks for validating arguments against the format string. Even if they do they would at most throw a warning, but still compile the program.
It's using the string "%s" as a format string, and using uninitialized memory as the "data".
The only reason it does "something" is because the compiler was apparently not smart enough to recognize that the format string required one parameter and zero parameters were supplied. Or because compiler warnings were ignored and/or errors were turned off.
Just an FYI for anybody who bumps into this: "Always leave all warnings and errors enabled and fix your code until they're gone" This doesn't guarantee correct behaviour but does make "mysterious" problems less likely.
In function with variable number of arguments, the "first argument" is "total number of arguments" being passed. But in printf() we never mention argument count. So how does it get to know about the total argument list ? How does printf() works ?
Let's look on printf declaration structure:
int printf(const char *format, ...)
format is actually the string that contains the text to be written to stdout.
The contained embedded format tags are later replaced by the values specified in subsequent additional arguments, and format is set accordingly as required.
You don't supply an argument count to printf - You do supply a format string, however - And that specifies how many arguments printfshould expect.
Very roughly speaking, the number of % signs in the format string is the argument count (although reality is a bit more complicated).
Conversion specifiers in the format string tell printf the number and types of arguments it should expect - for example, the format string "there are %d vowels in %s\n" tells printf to expect two arguments in addition to the format string, the first one being type int and the second being char *.
It's up to you to make sure the arguments match up with the format string. If you don't pass enough arguments, or the argument types don't match what the format string expects, then the behavior is undefined (most likely garbled output or a runtime error). If you pass too many arguments, the additional arguments are evaluated, but otherwise the function will operate normally as long as the format string is satisfied.
Edit
Chapter and verse
7.21.6.1 The fprintf function
...
2 The fprintf function writes output to the stream pointed to by stream, under control of the string pointed to by format that specifies how subsequent arguments are converted for output. If there are insufficient arguments for the format, the behavior is undefined. If the format is exhausted while arguments remain, the excess arguments are evaluated (as always) but are otherwise ignored. The fprintf function returns when the end of the format string is encountered.
This simple code is puzzling me - I am deliberately printing out more integers than I passed to printf. I expected an error. I got weird numbers - where are they coming from?
#include <stdio.h>
/* learn arrays */
void main(){
int pout;
pout = 6;
printf("%i %i %i\n%i %i %i\n%i %i %i\n", pout);
}
One example of the output:
6 608728840 0
-885621664 -885543392 608728816
0 0 -889304251
The single digits do not change with repeated runs, but the large integers do.
It's one of printf string format vulnerability. You are trying to call more argument than there actually are, so printf takes whatever he can on the stack.
It was (and still is) very used to exploit programs into exploring stacks to access hidden information or bypass authentication for example.
Viewing the stack
printf ("%08x %08x %08x %08x %08x\n");
This
instructs the printf-function to retrieve five parameters from the
stack and display them as 8-digit padded hexadecimal numbers. So a
possible output may look like:
40012980 080628c4 bffff7a4 00000005 08059c04
See this for a more complete explanation.
Because it's undefined behavior. If the number of specifiers is larger than the number of matching parameters or their types are incompatible, the behavior is undefined.
This qoute is from the c11 standard draft
7.21.6.1 The fprintf function
The fprintf function writes output to the stream pointed to by stream, under control of the string pointed to by format that specifies how subsequent arguments are converted for output. If there are insufficient arguments for the format, the behavior is undefined. If the format is exhausted while arguments remain, the excess arguments are evaluated (as always) but are otherwise ignored. The fprintf function returns when the end of the format string is encountered.
If a conversion specification is invalid, the behavior is undefined.282) If any argument is not the correct type for the corresponding conversion specification, the behavior is undefined.
I highlighted the relevant parts making them bold.
The int reserves some RAM but you didn't wrote anything in so it shows you what numbers are random somewhere in your RAM
so as printf() is a function and it returns the number of characters written if successful or negative value if an error occurred, looking at this example, the output as expected is zero.
#include <stdio.h>
int main(void)
{
printf("%d");
return 0;
}
now when I add some more of these %d : http://ideone.com/brw5vG
the output changes to this:
0 134513819 -1216430092 134513808
I am not able to figure out whats up with the random garbage values? There is a negative value here in the output as well, and a negative value justifies an error, so can anyone pinpoint what is the error here exactly?
Please be concise and specific. Thanks.
Because "%d" means an integer is expected. You don't pass any, so you get undefined behaviour.
Note that g++ 4.8.2 gives a useful warning:
warning: format '%d' expects a matching 'int' argument [-Wformat=]
similarly for clang++ 3.4:
warning: more '%' conversions than data arguments [-Wformat]
the output as expected is zero
printf("%d");
You should not expect anything as your program invokes undefined behavior.
(C99, 7.19.6.1p2) "[...] If there are insufficient arguments for the format, the behavior is
undefined.[...]"
You are mis-specifiying the format string to printf this is undefined behavior and you should have no expectations as to the result. By specifying %d you are telling printf to expect an int argument which you have not provided.
If we look at the C99 draft standard section 7.19.6.1 The fprintf function which also covers pritnf with respect to format specifiers says:
[...]If there are insufficient arguments for the format, the behavior is undefined.[...]
The problem is in how you pose the question; you assume it "should be 0." The fact is that this is undefined behavior, and printf will substitute for %d whatever happens to be in the stack.
Your code invokes undefined behavior. Anything could be happen.
The C11 Standard says in section 7.21.6 Formatted input/output functions:
If any argument is not the correct type for the corresponding conversion specification, the behavior is undefined.
You are passing no argument for the corresponding %d specifier.
There are 2 issues at hand: First is why doesn't the compiler issue an error about this bad call to printf(), and second is why you get garbage output. I'll answer them one at a time.
printf() is a tricky function. While most functions have a constant amount of arguments passed to them, printf() is different.
For example, if we take this simple function:
int max(int a, int b) {
if (a > b) return a;
else return b;
}
You can see that this function always receives 2 arguments. This is also something that the compiler knows, and enforces when you compile you code. This is why a call such as max(4) won't work. The compiler will see that we are passing max() 1 argument instead of 2 and it will issue an error.
printf() is a function that takes a variable amount of arguments, and this amount is determined by the amount of format specifiers (things that start with %) in the format string. This means that the compiler cannot know at compile time if the amount of arguments that you passed to printf is enough (or maybe too much).
The reason that you get garbage printed is because of how functions read their input arguments. All input arguments for a function reside on the stack. These are pushed into the stack before the function is called and later addressed by the function. In this case, printf() expects to have an extra argument besides the format string (because of the %d), and so it looks in the address where its 2nd argument might have been. Alas, that argument wasn't passed, so it will actually look into a place in the stack that might contain anything else (a return address, a frame pointer, a local variable of an enclosing scope or other).
You can read more about how function calls work here.
What's going on here:
printf("result = %d\n", 1);
printf("result = %f\n", 1);
outputs:
result = 1
result = 0.000000
If I ensure the type of these variables before trying to print them, it works fine of course. Why is the second print statement not getting implicitly converted to 1.00000?
In the second case you have a mismatch between your format string and the argument type - the result is therefore undefined behavio(u)r.
The reason the 1 is not converted to 1.0 is that printf is “just” a C function with a variable number of arguments, and only the first (required) argument has a specified type (const char *). Therefore the compiler “cannot” know that it should be converting the “extra” argument—it gets passed before printf actually reads the format string and determines that it should get a floating point number.
Now, admittedly your format string is a compile-time constant and therefore the compiler could make a special case out of printf and warn you about incorrect arguments (and, as others have mentioned, some compilers do this, at least if you ask them to). But in the general case it cannot know the specific formats used by arbitrary vararg functions, and it's also possible to construct the format string in complex ways (e.g. at runtime).
To conclude, if you wish to pass a specific type as a “variable” argument, you need to cast it.
An undefined behavior. An int is being treated as float
The short answer is that printf isn't really C++. Printf is a C function which takes a variable argument list, and applies the provided arguments to the format string basis the types specified in the format string.
If you want any sort of actual type checking, you should use streams and strings - the actual C++ alternatives to good old C-style printf.
Interesting, presumably it's fine if your put '1.0'
I suppose the printf only gets the address of the variable, it has no way of knowing what it was. But I would have thought the compiler would have the decency to warn you.