char type in va_arg - c

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.

Related

Does C compiler automatically cast literals to parameter types when passing to functions?

I have a doubt regarding the casting of types in C. Is it safe to pass literals to function parameters(expecting different types but compatible types) without explicitly casting? For example here is an example code:
#include<stdio.h>
int sum(int a, int b){
return a + b ;
}
int main(void){
int x = sum(6U,5U) ; /* Does the compiler automatically casts unsigned to int?
or do I need to explicitly cast it sum( (int) 6U, (int) 5U) ?*/
printf("%d \n", x);
return 0;
}
The code runs fine but I wanted to know if this legal or can lead to undefined behaviours (assuming no overflows) ?
If an argument in a function call corresponds to a parameter with a declared type, the argument is converted to the parameter type, per C 2018 6.5.2.2 7.
If the argument does not correspond to a parameter with a declared type, the default argument promotions are performed. This occurs when the argument corresponds to the ... part of a function prototype, per C 2018 6.5.2.2 7, or when called function does not have a prototype, per 6.5.2.2 6.
The default argument promotions consist of the integer promotions (which largely promote integer types narrower than [technically of lower rank] int to int) and promote float to double.
Note that the applicable function type for these conversions is the type of the expression used to call the function. A function may be defined in one place with a prototype, such as int foo(char *a) { ... }, but declared in another place without a prototype, such as int foo();. The declaration visible at the function call is what determines which conversions will be performed. This is particularly so when a function is called via a pointer.
Note that these conversions are not casts. A cast is a specific operator in source code, a type name in parentheses, for which a conversion is performed. The cast, such as (int) in source code, is different from the operation, the same way a + in source code is different from the addition performed for it.

I want default argument promotion's example

At C standard 6.5.2.2. paragraph 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.
"the expression that denotes the called function has a type that does not include a prototype" What can this be described as code?
char a = 1;
printf("%d", a); /*"the expression that denotes the called function" is printf, and `a`
doesn't have prototype(char). but a can't be replaced as `char a` originally. ...? */
This is my thought that can be applicable to above standard. Is this right?
For historical reasons, functions in C can be declared the old way, without a prototype, as:
type function()
or the new way, with a prototype, as:
type function(type parameter)
type function(type parameter, type parameter)
type function(type parameter, type parameter, type parameter)
… and so on
Thus, a function declaration in which the types of the parameters are given is said to have a prototype. This affects preparation of the arguments when calling a function. With a prototype, the arguments are converted to the declared types of the parameters. Without a prototype, the arguments are converted to default types, using rules called the default argument promotions. (Also, if a function is just being declared, not defined, the names of the parameters can be omitted, leaving a prototype that just lists types, as in type function(type, type, type).)
Because of the old grammar, to declare a function with a prototype that says it has no parameters, you need to explicitly say void: type function(void). (This is different in C++, which does not have to support the old grammar. In C++, type function() is a prototype with no parameters.)
Additionally, a prototype can specify that there are variable arguments by putting ,... after one or more regular parameters:
type function(type parameter,...)
In this case, the first arguments are converted to the parameter types, but the arguments that correspond to the ... are converted using the default argument promotions. printf is declared this way.
The default argument promotions are:
Integers narrower (technically, of lesser rank) than int are promoted to int if it can represent all the values of the source type or unsigned int otherwise.
float arguments are converted to double.
There is also some finickiness about bit-fields in the default argument promotions, which I cannot say has ever arisen in code for me.
History
In the old C grammar, a function would be defined with:
type function(name0, name1, name2)
type name0;
type name1;
type name2;
and it would be declared without a prototype, as with type function(). This means the caller did not know the actual types of the parameters. But you could not just pass a char value for a char argument or a short value for a short argument, because C, in trying to be flexible so it could work on many types of computers, had rules about char and short values being promoted to int in expressions. Additionally, character constants like 'X' have type int, not char, so, if somebody called a function with foo('X'), the compiler would not know if foo really wanted just a char rather than an int. So the default argument promotions were made to match the integer promotions.
Later versions of C fixed this by providing a way to declare the argument types in declarations visible to the caller, so the arguments always match the parameters (and the compiler has more information it can use to provide error messages). But the old grammar still has to be supported so that old code can be compiled.
More
The phrase in the C standard, “the expression that denotes the called function,” is used because you can call functions through pointers, not just their names. For example, we can write:
int (*FunctionPointer)() = (int (*)()) printf;
and then call printf using FunctionPointer("Hello, world.\n");. Then “the expression that denotes the called function” is FunctionPointer, and it does not have a prototype even though printf does. (There is no good reason to do this with the printf function, but some esoteric code may do some unsavory things.)
You can initially declare a function without a prototype:
int foo();
and later add a prototype:
int foo(float x, char *y);
The compiler will merge the declarations, and the resulting foo will have a prototype.
The expression that denotes the called function is printf, right.
And if you don't provide a prototype for this function, for example by not including <stdio.h> and not declaring of printf() yourself, the compiler recognizes the function as prototype-less.
In your case the first argument is a const char *, and the second argument is an int.
So, it is not a that has no prototype, but printf().
The value of a will be promoted to int.
The assumed prototype will be int printf(); and takes any number and any type of parameters.

What are function conversion rules for parameters in C?

I have understood how the conversion rules are for expression but I cannot find where or sum up to conversion related to function arguments. Any help please ?
If the argument matches a declared parameter from a function prototype, it is converted to that type.
Otherwise, there is either no function prototype or the prototype ends in ... and the argument matches the ellipsis, and the argument undergoes the default argument promotions.
Example:
void f(char, int, ...);
signed char a = 'x';
f( 25.8 // converted (truncated) to char matching parameter
, a // converted (promoted) to int matching parameter
, a // default-promoted to int (not matching any parameter)
, 1.f) // default-promoted to double (not matching any parameter)
Basically this is obsolete, functions have prototypes. But it lives on in variadic functions where you don't have a proper prototype. In reality these seem to be used almost exclusively for printf() style format strings, and most compilers have special case code built in to warn about non-matching types.
But shorts and chars are promoted to int, and floats are promoted to double. long integers remain long (so they need the %ld format), there's also long long (%lld format).

Confused about K&R c explanation on type conversions

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.

C compiler warning error

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).

Resources