I have a question regarding a warning message from the gcc compiler. The warning message occurs when an argument of scanf isn't a pointer to the variable that should carry the user input.
#include <stdio.h>
int main(int argc, const char *argv[]) {
float number;
scanf("%f", number); /* the value of 'number' is passed, instead of the adress to it */
return 0;
}
gcc will give the following warning message when it compiles the program.
scanf-problem.c: In function 'main':
scanf-problem.c:5:5: warning: format '%f' expects argument of type 'float *', but argument 2 has type 'double' [-Wformat=]
scanf("%f", number);
^
Like expected gcc wants the second argument of scanf to have the type 'float *' (the pointer to a float). What troubles me is that gcc believes that the second argument have the type 'double', when it actually have the type 'float'.
Which leads me to the question, why do gcc believe the second argument of scanf to be a double, when it actually is a float?
I have made some research on this topic to get an answer, but every answer I find is about how to get rid of the warning (write '&number' instead of 'number').
Which leads me to the question, why do gcc believe the second argument
of scanf to be a double, when it actually is a float?
Because float is promoted to double as specified in C Standard
6.5.2.2 Function calls
[#6] ... arguments
that have type float are promoted to double. These are
called the default argument promotions.
[#7] ... The ellipsis notation in a function prototype
declarator causes argument type conversion to stop after the
last declared parameter. The default argument promotions
are performed on trailing arguments.
You already know that you are passing wrong thing (as mentioned in comment) to scanf So just informing you reason behind the warning is float argument that you provide is first promoted to double.
Your compiler is right, the argument passed to scanf is indeed a double: for the ... part of the argument list a set of default conversions is performed. In particular all float arguments are promoted to double and this is what scanf gets to see.
[Edit] Other than posting the scanf() prototype, I now see the answer echoes #chill.
"imitation is the sincerest form of flattery."
Check the prototype for scanf()
int scanf(const char * restrict format, ...);
That ... means any number of any type of arguments are allowed. For historic reasons, all FP arguments of smaller than double are promoted to double when passed to such functions. That why the compiler calls it a double.
char, short are promoted to int too.
C11 6.5.2.2 6
" ... integer promotions are performed on each argument, and arguments that
have type float are promoted to double. These are called the default argument
promotions. ... "
The error message was misleading, scanf expect an address to the variable to fill the data back to it and you passed a value, and when scanf lookup that address it finds it to be different than what was suggested in formating "%f"
you can change it to
scanf("%f", &number); // &number instead of number
and it will work just fine
Related
I wondering why the compiler let this pass and is giving the right output, although sqrt() from its prototype normally should only get an double value as argument:
In C99 the declaration of the prototype is:
double sqrt (double x);
#include <stdio.h>
#include <math.h>
int main (void)
{
int i = 9;
printf("\t Number \t\t Square Root of Number\n\n");
printf("\t %d \t\t\t %f \n",i, sqrt(i));
}
Output:
Number Square Root of Number
9 3.000000
Why does the compiler not throw a warning at least and the given output is right, if I´m giving the sqrt() function an int as argument?
Is this crossing into Undefined Behavior?
I´m using gcc.
The Question was already asked twice for C++, but not for C, so my question is up for C.
I provide the links to the questions for C++ anyway:
Why does sqrt() work fine on an int variable if it is not defined for an int?
Why is sqrt() working with int argument?
This is not undefined behavior.
The function is defined to accept an argument of type double. Because the type of the argument is known, you can pass an int because it may be implicitly converted to a double. It's the same as if you did:
int i = 4;
double d = i;
The rules for conversion of function arguments are spelled out in section 6.5.2.2p7 of the C standard regarding the function call operator ():
If the expression that denotes the called function has a type that
does include a prototype, the arguments are implicitly converted, as
if by assignment, to the types of the corresponding parameters, taking
the type of each parameter to be the unqualified version of its
declared type. The ellipsis notation in a function prototype
declarator causes argument type conversion to stop after the last
declared parameter. The default argument promotions are performed on
trailing arguments
In contrast, if you passed an int to printf when the format string expects a double, i.e.:
printf("%f\n", 4);
Then you have undefined behavior. This is because the types of the arguments are not known at compile time so the implicit conversion can't happen.
The following code works:
int main(void)
{
float f = get_float();
int i = round(f*100);
printf("%i\n", i);
}
Yet, error generated if coding this way:
printf("%i\n", round(1.21*100));
Output says round(1.21*100) is float. So, then why
int i = round(f*100);
is fine?
When you do
int i = round(f*100);
you convert the result of the double function round. The converted result is stored in the int variable i, which can be used with the format "%i" as it expects an int argument.
When you pass the double result of round directly as an argument to a format that expects an int you have mismatching format and argument types. That leads to undefined behavior.
No conversion is made in the call to printf, and no conversion can be made since the code inside the printf function doesn't know the actual type of the argument. All it knows is the format "%i". All possible type-information is lost for variable-argument functions.
This is because of the behavior of automatic type casting. In printf, automatic typecasting does not work. When you say %i, it simply expects integer, it can not convert double to integer and then print.
In assignment operation, double is converted to integer first and then it is assigned to left operand of the = operator. I hope this helps.
This is a bit of duplication, but maybe helps for a better understanding:
round() has the following prototype:
double round(double x);
so it returns double.
There is an implicit conversion from double to int in C, so writing
int i = round(f*100);
will convert the result of round() to int.
If you have a function that expects an int, e.g.
void printMyNumber(int number)
{
printf("%i\n", number);
}
you can call it like this:
printMyNumber(round(f*100));
and the implicit conversion works as expected, because the compiler sees both types (the return type from round() and the expected argument type of printMyNumber()).
The reason this doesn't work with printf() is that the prototype of printf() looks like this:
printf(const char *format, ...);
so, except for the first argument, the types of the arguments are unknown. Therefore, whatever you pass is passed without any conversion (except for default argument promotions). Of course, you could use a cast to achieve an explicit conversion instead:
printf("%i\n", (int) round(f*100)); // <- this is fine
A simple program
#include<stdio.h>
int main() {
char b='a';
printf("%s \n", b);
return 0;
}
Output:
test.c: In function ‘main’:
test.c:4:1: warning: format ‘%s’ expects argument of type ‘char *’, but argument 2 has type ‘int’ [-Wformat=]
printf("%s \n", b);
^
But the second arg is char not int.
Is this a wrong warning by the compiler, or something else I am missing?
The compiler is certainly correct to warn about the call. printf with a "%s" specifier requires an argument of type char* (which must point to a string), and the argument you pass is not of that type.
As for why the warning message refers to int rather than char, it's because printf is a variadic function. The first parameter, the format string, is declared to be of type const char*, but the following arguments are specified only as , .... In this special case, arguments of integer types narrower than int are promoted to int or to unsigned int. So even though the expression b is of type char, the actual argument that's passed to printf is of type int; specifically, it's the result of converting the value of b from char to int.
The warning is accurate, though the reasons are modestly obscure.
The declaration of printf() is:
int printf(const char *restrict format, ...);
The const and restrict keywords aren't important for this discussion. What is important is the ellipsis, .... When arguments are passed to a function with a variable argument list (a variadic function), they undergo 'default argument conversions'. Integer types shorter than int (short and char in their various forms) are promoted to int, and float values are promoted to double. Thus, the value of b is converted to int by the rules for calling a variadic functions.
The "%s" format expects to be given a pointer to a null-terminated character string. The variable b is a single char, not a string. Consequently, the compiler is correctly warning you that you will not get good results from running the program.
In this context, using a format such as %c (to print a character) or %d (to print a decimal integer) is probably best:
printf("%c\n", b);
As a general rule, at this stage in your C programming career, you should assume the compiler is right and that you're wrong. Remember, the C compiler knows a lot more about C than you do. This isn't to say that there are never bugs in compilers. However, the chances of you finding one are slim. Until you know enough about C (maybe in five to ten years time), then you should assume the compiler is right, you're wrong, and work out (a) what the compiler means and (b) how to fix it.
It was not always thus. Thirty years ago, bad compilers existed because the best were not all that much better. It was possible to find bugs in those compilers. There has, however, been a serious winnowing and few incompetent compilers are left on the market. One area where you can sometimes find compilers with surprising limitations (occasionally tantamount to bugs) is in specialized embedded systems for obscure chips. However, in mainstream o/s for desktops and servers (and tablets and smart phones), you're unlikely to come across a seriously defective compiler.
ISO/IEC 9899:2011 §6.5.2.2 Function calls
¶6 If the expression that denotes the called function has a type that does not include a
prototype, the integer promotions are performed on each argument, and arguments that
have type float are promoted to double. These are called the default argument
promotions. If the number of arguments does not equal the number of parameters, the
behavior is undefined. If the function is defined with a type that includes a prototype, and
either the prototype ends with an ellipsis (, ...) or the types of the arguments after
promotion are not compatible with the types of the parameters, the behavior is undefined.
If the function is defined with a type that does not include a prototype, and the types of
the arguments after promotion are not compatible with those of the parameters after
promotion, the behavior is undefined, except for the following cases:
one promoted type is a signed integer type, the other promoted type is the
corresponding unsigned integer type, and the value is representable in both types;
both types are pointers to qualified or unqualified versions of a character type or
void.
The 'integer promotions' are defined in §6.3.1.8 Usual arithmetic conversions. They're more complex than I want to go through here.
a char is really just an 8 bit number, so a single char alone is no different than an int, except that it can only store smaller numbers. In your printf statement you have
%s
but a single char is not considered a string, just a number, so you should use
%c or %d
for you printf statement. If you had an array of chars, then you would use %s
Try this instead:
printf("%c \n", b);
The compiler is casting the char to int on the fly, so the error msg makes sense (at least to someone used to interpreting C compiler messages).
I just wrote something to test the sizeof operator.
Firstly, when I build this program, GCC give me a warning.
main(){
printf("%d", (2*3.14f));
}
// test.c|2|warning: format '%d' expects argument of type 'int',
// but argument 2 has type 'double' [-Wformat=]
So GCC believes the type of (2*3.14f) is double.
Then I added a sizeof operator, I assume the output will be exactly 8, which is the size of double.
main(){
printf("%d", sizeof(2*3.14f));
}
//Output: 4
This is really confusing. So the question is: what is the type of (2*3.14f)?
(2*3.14f) has type float. It is promoted to double when passed to a variadic function, hence the reference to double in the error message from GCC.
If you wish to display a float converted to int, use printf("%d", (int)…);
If you wish to display the bits of a float as if it were an int, use:
int i;
assert(sizeof(f) == sizeof(i));
memcpy(&i, &f, sizeof(f));
printf("%d", i);
Both invocations have undefined behaviour. Indeed, the first argument has type double and the second size_t, so you must use format specifiers %f and %zu, respectively.
(The type of 2 * 3.14f is float, because of the usual arithmetic conversions, but the argument is promoted to double under the floating point conversions because you're passing it as a variable argument.)
int main()
{
int x,y;
int z;
char s='a';
x=10;y=4;
z = x/y;
printf("%d\n",s); //97
printf("%f",z); //some odd sequence
return 0;
}
in the above piece of code the char s is automatically converted to int while printing due to the int type in control string, but in the second case the int to float conversion doesn't happen. Why so?
In both cases the second argument is promoted to int. This is how variadic functions work, and has nothing to do with the format string.
The format string is not even looked at by the compiler: it's just an argument to some function. Well, a really helpful compiler might know about printf() and might look at the format string, but only to warn you about mistakes you might have made. In fact, gcc does just that:
t.c:9: warning: format ‘%f’ expects type ‘double’, but argument 2 has type ‘int’
It is ultimately your responsibility to ensure that the variadic arguments match the format string. Since in the second printf() call they don't, the behaviour of the code is undefined.
Functions with variable number of arguments follow the rule of the default argument promotion. Integer promotion rules are applied on arguments of integer types and float arguments are converted to double.
printf("%d\n",s);
sis a char and is converted to int.
printf("%f",z);
z is already an int so no conversion is performed on z
Now the conversion specifier f expects a double but the type of the object after the default argument promotion is an int so it is undefined behavior.
Here is what C says on arguments of library functions with variable number of arguments
(C99, 7.4.1p1) "If an argument to a function has [...] a type (after promotion) not expected by a function with variable number of arguments, the behavior is undefined."
The char is not being promoted to int due to the control string. The char is working as an int because all data that is less than 4 bytes when passed to printf is bumped up to 4 bytes, which is the size of an int, because of the cdecl calling convention of variadic functions (the point of this is so that the data that comes next will be aligned on a 4-byte boundary on the stack).
printf is not type-safe and has no idea what data you really pass it; it blindly reads the control string and extracts a certain number of bytes from the stack based on what sequences it finds, and interprets that set of bytes as the datatype corresponding to the control sequence. It doesn't perform any conversions, and the reason you are getting some wierd printout is because the bits of an int are being interpreted as the bits of a float.
due to the int type in control string
That is incorrect. It is being converted because shorter int types are promoted to int by the var_args process. Int types are not converted to float types because the va/preprocessor doesn't know what formats are expected.