问题
How exactly do variadic functions treat numeric constants? e.g. consider the following code:
myfunc(5, 0, 1, 2, 3, 4);
The function looks like this:
void myfunc(int count, ...)
{
}
Now, in order to iterate over the single arguments with va_arg
, I need to know their sizes, e.g. int
, short
, char
, float
, etc. But what size should I assume for numeric constants like I use in the code above?
Tests have shown that just assuming int
for them seems to work fine so the compiler seems to push them as int
even though these constants could also be represented in a single char
or short
each.
Nevertheless, I'm looking for an explanation for the behaviour I see. What is the standard type in C for passing numeric constants to variadic functions? Is this clearly defined or is it compiler-dependent? Is there a difference between 32-bit and 64-bit architecture?
Thanks!
回答1:
I like Jonathan Leffler's answer, but I thought I'd pipe up with some technical details, for those who intend to write a portable library or something providing an API with variadic functions, and thus need to delve in to the details.
Variadic parameters are subject to default argument promotions (C11 draft N1570 as PDF; section 6.5.2.2 Function calls, paragraph 6):
.. 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 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
Floating-point constants are of type double
, unless they are suffixed with f
or F
(as in 1.0f
), in which case they are of type float
.
In C99 and C11, integer constants are of type int
if they fit in one; long
(AKA long int
) if they fit in one otherwise; of long long
(AKA long long int
) otherwise. Since many compilers assume an integer constant without a size suffix is a human error or typo, it is a good practice to always include the suffix if the integer constant is not of type int
.
Integer constants can also have a letter suffix to denote their type:
u
orU
forunsigned int
l
orL
forlong int
lu
orul
orLU
orUL
orlU
orLu
oruL
orUl
forunsigned long int
ll
orLL
orLl
orlL
forlong long int
llu
orLLU
(orULL
or any of their uppercase or lowercase variants) forunsigned long long int
The integer promotion rules are in section 6.3.1.1.
To summarize the default argument promotion rules for C11 (there are some additions compared to C89 and C99, but no significant changes):
float
are promoted todouble
All integer types whose values can be represented by an
int
are promoted toint
. (This includes both unsigned and signedchar
andshort
, and bit-fields of types_Bool
,int
, and smallerunsigned int
bit-fields.)All integer types whose values can be represented by an
unsigned int
(but not anint
) are promoted tounsigned int
. (This includesunsigned int
bit fields that cannot be represented by anint
(ofCHAR_BIT * sizeof (unsigned int)
bits, in other words), and typedef'd aliases ofunsigned int
, but that's it, I think.)Integer types at least as large as
int
are unchanged. This includes typeslong
/long int
,long long
/long long int
, andsize_t
, for example.
There is one 'gotcha' in the rules that I'd like to point out: "signed to unsigned is okay, unsigned to signed is iffy":
If the argument is promoted to a signed integer type, but the function obtains the value using the corresponding unsigned integer type, the function obtains the correct value using modulo arithmetic.
That is, negative values will be as if they were incremented by (1 + maximum representable value in the unsigned integer type), making them positive.
If the argument is promoted to an unsigned integer type, but the function obtains the value using the corresponding signed integer type, and the value is representable in both, the function obtains the correct value. If the value is not representable in both, the behaviour is implementation-defined.
In practice, almost all architectures do the opposite of above, i.e. the signed integer value obtained matches the unsigned value substracted by (1 + the largest representable value of the unsigned integer type). I've heard that some strange ones may signal integer overflow or something similarly weird, but I have never gotten my mitts on such machines.
The man 3 printf man page (courtesy of the Linux man pages project) is quite informative, if you compare the above rules to printf specifiers. The make_message()
example function at the end (C99, C11, or POSIX required for vsnprintf()
) should also be interesting.
回答2:
When you write 1
, that is an int
constant. There is no other type that the compiler is allowed to use. If there is a non-variadic prototype for the function that demands a different type, the compiler will convert the integer 1
to the appropriate type, but on its own, 1
is an int
constant. So, in your example, all 6 arguments are int
.
You have to know the types of the arguments somehow before the called variadic function processes them. With the printf()
family of functions, the format string tells it what to expect; similarly with the scanf()
family of functions.
Note that the default conversions apply to the arguments corresponding to the ellipsis of a variadic function. For example, given:
char c = '\007';
short s = 0xB0hD;
float f = 3.1415927;
a call to:
int variadic_function(const char *, ...);
using:
int rc = variadic_function("c s f", c, s, f);
actually converts both c
and s
to int
, and f
to double
.
来源:https://stackoverflow.com/questions/40320893/variadic-functions-and-constants