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).
Related
I have the following function which writes passed arguments to a binary file.
void writeFile(FILE *fp, const int numOfChars, ...)
{
va_list ap;
va_start(ap, numOfChars);
for(int i = 0; i < numOfChars; i++)
{
const char c = va_arg(ap, char);
putc(c, fp);
}
va_end(ap);
}
Upon compiling, I get the following warning from the compiler
warning: second argument to 'va_arg' is of promotable type 'char'; this va_arg
has undefined behavior because arguments will be promoted to 'int' [- Wvarargs]
Now as I understand it, C wants to promote char type to int. Why does C want to do this? Second, is the best solution to cast the int back to a char?
Now as I understand it, C wants to promote char type to int. Why does C want to do this?
Because that's what the standard says. If you pass an integral value with conversion rank smaller than that of int (e. g. char, bool or short) to a function taking a variable number of arguments, it will be converted to int. Presumably the reason for this has its roots in performance, where it was (and in fact, often it still is nowadays) better to pass values aligned to a machine word boundary.
Second, is the best solution to cast the int back to a char?
Yes, but you don't really need a cast even, an implicit conversion will do:
char ch = va_arg(ap, int);
Variadic functions are treated specially.
For a non-variadic function, the prototype (declaration) specifies the types of all the parameters. Parameters can be of any (non-array, non-function) type -- including types narrower than int.
For a variadic function, the compiler doesn't know the types of the parameters corresponding to the , .... For historical reasons, and to make the compiler's job easier, any corresponding arguments of types narrower than int are promoted to int or to unsigned int, and any arguments of type float are promoted to double. (This is why printf uses the same format specifiers for either float or double arguments.)
So a variadic function can't receive arguments of type char. You can call such a function with a char argument, but it will be promoted to int.
(In early versions of C, before prototypes were introduced, all functions behaved this way. Even C11 permits non-prototype declarations, in which narrow arguments are promoted to int, unsigned int, or double. But given the existence of prototypes, there's really no reason to write code that depends on such promotions -- except for the special case of variadic functions.)
Because of that, there's no point in having va_arg() accept char as the type argument.
But the language doesn't forbid such an invocation of va_arg(); in fact the section of the standard describing <stdarg.h> doesn't mention argument promotion. The rule is stated in the section on function calls, N1570 6.5.2.2 paragraph 7:
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.
And the description of the va_arg() macro, 7.16.1.1, says (emphasis added):
If there is no actual next argument, or if type is not compatible with
the type of the actual next argument (as promoted according to the
default argument promotions), the behavior is undefined, except for
the following cases:
[SNIP]
The "default argument promotions" convert narrow arguments to int, unsigned int, or double. (An argument of an unsigned integer type whose maximum value exceeds INT_MAX will be promoted to unsigned int. It's theoretically possible for char to behave this way, but only in a very unusual implementation.)
Second, is the best solution to cast the int back to a char?
No, not in this case. Casts are rarely necessary; in most cases, implicit conversions can do the same job. In this particular case:
const char c = va_arg(ap, char);
putc(c, fp);
the first argument to putc is already of type int, so this is better written as:
const int c = va_arg(ap, int);
putc(c, fp);
putc internally converts its int argument value to unsigned char and writes it to fp.
The book says the following on page 45:
Since an argument of a function call is an expression, type conversions also take place when arguments are passed to functions. In the absence of a function prototype, char and short become int, and float becomes double. This is why we have declared function arguments to be int and double even when the function is called with char and float.
I don't understand what the last sentence there is saying. Can someone lead me in the right direction?
We can see that happen here. According to cplusplus.com, this is the declaration of printf():
int printf(const char * format, ...);
The ... means this function can take an unknown number of parameters of unspecified types, and because it is unspecified, the standardization of numeric types to int and double happens to all printf() parameters except the first, that was specified.
Example:
char x = 10;
short y = 100;
int z = 1000;
printf("Values of char is %d, short is %d, and int is %d", x, y, z);
All those integer types are automatically recasted to int when passed to printf(). We can see that as %d works for all of them.
Note that types bigger than double and int are not converted, such as long int, long double, long long etc. Those types are 64-bits.
When you use a prototype for a function in C (ansi C, as original K&R specification didn't define parameters this way) you declare a formal parameter as having a type. When you match it in an actual expression, two things can happen:
The formal parameter and the actual expression are the same type. In this case, every thing is fine and the expression value is used to initialize the parameter prior to call the function.
The formal parameter and the actual expression are not the same type. In that case, the compiler tries to do automatic type conversion if possible from the type of the actual expression to the formal parameter type.
In case no prototype is found, the rules you put above mandate, so chars and shorts get promoted to int values, and al the floating type values get promoted to double.
The last phrase in your quoted paragraph tells you that in some example (not shown) that types are being used for formal parameters to make sure actual expressions get converted to the types of formal parameters.
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 9 years ago.
Improve this question
I'm new to learning the C programming language and this question is confusing for me because I thought the compiler will have an error.
Assuming a function is called before its definition/prototype occurs, which one of the following is false concerning its arguments?
A. All char types are converted to type int.
B. All short types are converted to type int.
C. All float types are converted to type double.
D. All types other than char, short, and float are passed unaltered.
E. Each argument is converted to the type of its corresponding formal parameter.
If you have a function call in C89 or C90 (same thing; one is ANSI C, t'other is ISO C), then:
The return type is assumed to be int.
Type char (and signed char and unsigned char) and short (and unsigned short) are converted to int (or, exceptionally, unsigned int if it was unsigned short and sizeof(unsigned short) == sizeof(unsigned int)), and float is converted to double.
Note that pointers are not changed.
The function is assumed not to be a varargs function. You must have a prototype in scope to call a varargs function such as printf() or scanf() correctly.
So, the answer is E.
The standard (current version, ISO/IEC 9899:2011) says:
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.
Note that getting it 'wrong' leads to undefined behaviour, and undefined behaviour should be avoided at all costs. The program could do anything — including erasing all the files on your computer — and that is acceptable according to the standard. Actually, programs seldom do that (and even more seldom cause 'demons to fly out of your nose', leading to the phrase 'nasal demons' (search for it), but that would also be acceptable in the face of undefined behaviour).
You should make it a policy that you never call a function without a prototype in scope if at all possible. All new code should meet that criterion; if you are unlucky enough to have to maintain ancient code that doesn't, you may have to go with the flow, but aim to get to the point where all your code can be compiled with options such as -Wmissing-prototypes -Wstrict-prototypes -Wold-style-declaration -Wold-style-definition -Werror in effect. Those are GCC options that report on problems with function prototypes and function definitions.
Thanks everyone!
I found the answer.
+All subintegers (char and short) are converted to type int or unsigned.
+All type float are converted to type double.
+All other types are unaltered.
So yeah the answer is E.
Thanks for the moral support!
See what happens when a function call is made if
1. Compiler has encountered a prototype prior to yhe call:
The value of each argument converted implicitly to the type of the corresponding parameters as if by assignment.
2. The compiler has not encountered a prototype prior to the call:
The compiler will perform default argument promotions: (1) float arguments are converted to double. (2) the integral promotions are performed, causing char and short arguments to be converted to int (In C99, integer promotions are performed).
So the answer is
E. Each argument is converted to the type of its corresponding formal parameter.
But keep in mind that relying on default promotions is dangerous. Consider this example:
#include <stdio.h>
int main(void)
{
double x = 3.0;
printf("Square: %d\n", square(x));
return 0;
}
int square(int n)
{
return n*n;
}
At the time square is called, the compiler hasn't seen a prototype yet, so it doesn't know that square expects an argument of type int. Instead, the compiler performs the default argument promotions on x, with no effect. Since it expecting an argument of type int but has been given a double value instead, the effect of calling square is undefined.
I was wondering, why is casting implicitly from an integer to a char possible in C ?
If for example :
int i = 2789;
printf("%c\n",i);
would give me a char back, because it would have truncated the bits starting from the most significant ones.
But usually you can't really cast implicitly if it means you'll lose in precision so why can I do it here ?
There is no implicit cast going on here.
printf is a variadic function, which means that any arguments of type char are converted up to int before the function is called. Your argument is already of type int, so no conversion occurs.
All integral arguments passed to a function are generally promoted to the CPU's word size, so regardless if you pass a char, short, int or long, the physical layout of the stack and/or registers upon entry to printf is the same anyway.
Therefore, there's no "cast" going on, merely normal argument passing.
That's not to say that the code is correct, however. Argument passing and type promotion is defined in the ABI, not by C, so it may not work on all platforms. (Though it will work on the vast majority.)
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.