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.