why printf("%d", arg) works with arg of type int, short, char


J

jononanon

Hi there!

Check this out -> the following code always works:

printf("%d %d %d", (char)'\1', (short)2, 3);
// prints: 1 2 3

So a conversion specifier of %d works with type int, or "SMALLER" integer-types (short or char) !!!

Why does this work??
Is my explanation correct?




->
With printf("%d", (char) '\1')
the argument 1 lands on the stack not as char parameter, but as a parameterpromoted to int, and the library code that handles the access to the parameter uses va_arg(ap, int) to access it. You can never use va_arg(ap, char) -> that would distroy a consistent handling of the stack-parameters!


When handling parameters on the stack, the size from one parameter to the next is never smaller than an int. (Even when NOT using variable arguments. Right? -> ref in C-std??)
Is this called argument promotion?


Demo:
/*
demonstration that char on stack nonetheless occupies the space of an int!
Not only does it occupy the space of an int, but all bytes comprising that int are set correctly! not just the single byte corresponding the the char.
(How does one call this: argument promotion????
Or is this part of integer promotion of a literal????)
*/
int func(char a, char b, char c)
{
const char *p = &a;
printf("a=%d\n"
"b=%d\n"
"c=%d\n", p[0],
p[-(int)sizeof(int)],
p[-(int)sizeof(int) * 2]);
/* don't do this. might probably work on x86 with gcc (but again: don't do this)
If you want to do something like this portable, please use stdarg.h instead!
*/
}

Now, since printf("%d", arg) with its variable argument list, is implemented in the C library with stdarg.h's va_arg(ap, type)
and type may not be a "smaller" type than int, it so happens that the particular code for handling the conversion specifier "%d" uses va_arg(ap, int).


And that's the explanation!

Is it correct?






Can someone point out relevant parts of the C11 standard, that show that this handling of parameters in the stack must never occupy space of less thansizeof(int)??
And: ahhh... is this a issue of handling the stack; OR part of integer promotion of char-literals?? Two separate issues, or one and the same??







Examples with questions:
1)
printf("%d", '\1');

Here '\1' is promoted to int (integer promotion) and then put on the stack as integer?


2)
printf("%d", (char)'\1');

Does normal integer promotion of the literal char occur here? I think no.
Here a different type of promotion occurs, which is the one of handling theparameter stack??



I realize that I'm a bit confused, and may have things half-correct or so....?
Can someone give a good explanation.
Thanks so much!!

J.
 
Ad

Advertisements

J

jononanon

In func() above

Note that the code also "works" (yes yes... use stdarg.h instead!) when p is changed to be an int-pointer!!!

int func(char a, char b, char c)
{
const int *p = &a; // difference to prev: int pointer!!!!
printf("a=%d\n"
"b=%d\n"
"c=%d\n", p[0],
p[-(int)sizeof(int)],
p[-(int)sizeof(int) * 2]);
/* don't do this. might probably work on x86 with gcc (but again: don't do this)
If you want to do something like this portable, please use stdarg.h instead!
*/
}
 
J

jononanon

Ah damn, I've found some mistakes that have crept in...

I wrote:
"but all bytes comprising that int are set correctly! not just the single byte corresponding the the char."

This is not correct. Rather:
Only the space the char occupies on the stack is sizeof(int), but NOT ALL BYTES comprising that int are correctly set to correspond to the value of the char.

Proof is a FIX OF MY 2ND POST ABOVE (which was incorrect):

int func(char a, char b, char c)
{
const int *p = &a; // difference to prev: int pointer!!!!
- hide quoted text -
printf("a=%d\n"
"b=%d\n"
"c=%d\n", p[0],
p[-1], // FIX
p[-2]); // FIX
/* don't do this. might probably work on x86 with gcc (but again: don't do this)
If you want to do something like this portable, please use stdarg.h instead!
*/
}


This shows that the other bytes that are part of the stack-space (sizeof(int)), are not set correctly!

So then:
only when using variable arguments, are arguments of type char (or short) placed on the stack as correspondingly promoted int.

Ahhhh.... Sortof correct????? Maby??
 
M

Melzzzzz

On Sat, 1 Mar 2014 01:29:54 -0800 (PST)
Hi there!

Check this out -> the following code always works:

printf("%d %d %d", (char)'\1', (short)2, 3);
// prints: 1 2 3

So a conversion specifier of %d works with type int, or "SMALLER"
integer-types (short or char) !!!

Why does this work??

Architecture/abi is that way.
Is my explanation correct?




->
With printf("%d", (char) '\1')
the argument 1 lands on the stack not as char parameter, but as a
parameter promoted to int, and the library code that handles the
access to the parameter uses va_arg(ap, int) to access it. You can
never use va_arg(ap, char) -> that would distroy a consistent
handling of the stack-parameters!

On x86-64 first 6(Unix)/4(Windows) parameters are passed through
64 bit registers. Library code does use va_arg(ap,int)
but value is correct as accessing parts of 64 bit register
is correct. Same with 32 bit , but with stack of 32 bit
bit parameters. I guess that if parameter is long
long (in case of 32bit program) your logic breaks.
I'm not sure how long long is passed but it cannot
fit in 32 bit parameter, so either it has to be split
in two parameters or pointer to it is passed.
In any case I think that would break char/int parameters.
 
K

Keith Thompson

Hi there!

Check this out -> the following code always works:

printf("%d %d %d", (char)'\1', (short)2, 3);
// prints: 1 2 3

So a conversion specifier of %d works with type int, or "SMALLER"
integer-types (short or char) !!!

Why does this work??
Is my explanation correct?
[...]

N1570 (C11 draft) 6.5.2.2p7:

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.

The default argument promotions convert float to double, and any integer
type narrower than int is convert to int if int can represent all the
values of the type, otherwise to unsigned int. (The latter avoids
overflow when an unsigned short value is promoted and short and int are
the same size.)

That's why, among other things, there are no specific printf formats for
float or short.
 
J

jononanon

I'm not sure how long long is passed but it cannot

fit in 32 bit parameter, so either it has to be split

in two parameters or pointer to it is passed.

In any case I think that would break char/int parameters.
Yes, true:

printf("%lld %lld %lld\n", 1, 2, 3); /* stuffs up, if sizeof(int) < sizeof(long long) */
 
Ad

Advertisements

J

jononanon

any integer

type narrower than int is convert to int if int can represent all the

values of the type, otherwise to unsigned int. (The latter avoids

overflow when an unsigned short value is promoted and short and int are

the same size.)
Is this conversion to unsigned in really important?
I mean in terms of overflow! -> who cares? Isn't solely the bit-pattern important??
Because: Even if conversion were ONLY to signed int, the result via
va_arg(ap, int), or via va_arg(ap, unsigned int) is what is important. And that is not controlled by the default argument promotion, but instead by the printf-conversion-specifier. (Am I missing something?)
 
B

BartC

printf("%d %d %d", (char)'\1', (short)2, 3);
// prints: 1 2 3

So a conversion specifier of %d works with type int, or "SMALLER"
integer-types (short or char) !!!
Can someone point out relevant parts of the C11 standard, that show that
this handling of parameters in the stack must never occupy space of less
than sizeof(int)??
And: ahhh... is this a issue of handling the stack; OR part of integer
promotion of char-literals?? Two separate issues, or one and the same??
Examples with questions:
1)
printf("%d", '\1');

Here '\1' is promoted to int (integer promotion) and then put on the stack
as integer?
2)
printf("%d", (char)'\1');

Does normal integer promotion of the literal char occur here? I think no.
Here a different type of promotion occurs, which is the one of handling
the parameter stack??

The simple answer is that promotion is used to int, for values such as short
and char, and for integer (and char) constants that will fit into an int.

C likes using using int for calculations, even when evaluating char+char,
unless the expression involves floating point, pointers, or longer ints.
(Mixing signed and unsigned is another discussion.)

The stack thing is an implementation issue, not a language one, and may or
may not be relevant anyway. (For example, a machine might require the stack
to be 16-byte aligned, but your ints might still only be 4 bytes.)
 
J

James Kuyper

Is this conversion to unsigned in really important?
I mean in terms of overflow! -> who cares? Isn't solely the bit-pattern important??

No - because there are real machines where overflow has bad consequences.
Because: Even if conversion were ONLY to signed int, the result via
va_arg(ap, int), or via va_arg(ap, unsigned int) is what is important. And that is not controlled by the default argument promotion, but instead by the printf-conversion-specifier. (Am I missing something?)

If the type of the argument, after promotion, differs from the type that
you specify in the corresponding invocation of va_arg(), the behavior is
undefined - such code might work, but you shouldn't count on it. When
it's just the difference between a given signed type and the
corresponding unsigned type, it's fairly safe, so long as only positive
values are involved: such are guaranteed to have the same representation
both types. No such guarantee applies to int and unsigned short, even if
they both have the same size - in principle, at least, a fully
conforming implementation could, for example, have big-endian int, and
little-endian unsigned short.

In this particular case, you can probably get away with sloppy coding
that ignores these issues - but it's better to handle it correctly. If
there's a possibility that a given value that is subject to the default
promotions might promote to either int or unsigned int, depending upon
the characteristics of the implementation you're using, you should write
your code to either remove that possibility, or deal with it.
 
K

Kenny McCormack

Barry Schwarz said:
There is no requirement for the char to occupy sizeof(int) bytes on
the stack.

Donning clc-pedant hat (which, admittedly, doesn't fit me very well):

There is no requirement for there to be a stack.

And, generally, you can get yourself into trouble with the Kiki-squad if
you use "the s-word" around here.

--
"That's the eternal flame."[7] The single became another worldwide hit.[8]

Hoffs was actually naked when she recorded the song, after being convinced
by Sigerson that Olivia Newton-John got her amazing performances by
recording everything while naked.[9]

(From: http://en.wikipedia.org/wiki/The_Bangles)
 
E

Eric Sosman

[...]
C likes using using int for calculations, even when evaluating
char+char,[...]

There's a good reason for using int arithmetic on sub-int
types: Many machines nowadays cannot perform sub-int arithmetic.
The prevailing style is to fetch operands from memory into CPU
registers, do register-to-register arithmetic, and (perhaps)
store the result back to memory again. Well, how wide are those
registers? They're often 32 or 64 bits wide, so what is one to
do with 8-bit or 16-bit values in memory? Merely loading a
narrow value into a wider register necessarily widens it.

Yes, there are machines with narrower registers: 16-bit and
8-bit CPU's are not hard to find. But even those must engage
in promotion now and then:

struct {
unsigned int x : 3;
signed int y : 7;
} u;
u.x = 5;
u.y = 8;
u.y += u.x;

Unless you've got some three- and seven-bit registers lying around
(and instructions to do three-plus-seven-bit arithmetic), some kind
of widening simply has to take place. Even on machines that can do
memory-to-memory arithmetic without involving registers, bit-fields
of peculiar widths almost certainly require widening.

In short, C's rules for integer promotions are a recognition
that CPU's usually offer fewer integer types than C itself does,
so some kind of C-to-CPU-and-back mapping is needed.
 
Ad

Advertisements

K

Kenny McCormack

On the ball there. Well done. Kiki and co should be in to agree with you
any moment now...

And that is, after all, every CLC posters primary goal - to be acknowledged
and accepted by Kiki and his coterie.

It is certainly mine...

--
Modern Christian: Someone who can take time out from
complaining about "welfare mothers popping out babies we
have to feed" to complain about welfare mothers getting
abortions that PREVENT more babies to be raised at public
expense.
 
G

glen herrmannsfeldt

Eric Sosman said:
[...]
C likes using using int for calculations, even when evaluating
char+char,[...]
There's a good reason for using int arithmetic on sub-int
types: Many machines nowadays cannot perform sub-int arithmetic.
The prevailing style is to fetch operands from memory into CPU
registers, do register-to-register arithmetic, and (perhaps)
store the result back to memory again. Well, how wide are those
registers? They're often 32 or 64 bits wide, so what is one to
do with 8-bit or 16-bit values in memory? Merely loading a
narrow value into a wider register necessarily widens it.

I believe that many other languages with different width integers
don't allow for this, or at least don't require it. In Fortran,
for example, if you run a DO loop with 8 bit integers, with the
largest value as the loop end value, the loop might not end.

PL/I allows one to ask for exactly the number of bits needed,
such that the system supplies at least that many. As well as I
remember it, operations on smaller than the maximum value allow
for larger results, until you reach the maximum.
Yes, there are machines with narrower registers: 16-bit and
8-bit CPU's are not hard to find. But even those must engage
in promotion now and then:
struct {
unsigned int x : 3;
signed int y : 7;
} u;
u.x = 5;
u.y = 8;
u.y += u.x;
Unless you've got some three- and seven-bit registers lying around
(and instructions to do three-plus-seven-bit arithmetic), some kind
of widening simply has to take place. Even on machines that can do
memory-to-memory arithmetic without involving registers, bit-fields
of peculiar widths almost certainly require widening.

The compiler could generate instructions to wrap as appropriate
for those operations. Masking off high bits of unsigned values is
pretty easy on most systems. Sign extension isn't always so easy.
In short, C's rules for integer promotions are a recognition
that CPU's usually offer fewer integer types than C itself does,
so some kind of C-to-CPU-and-back mapping is needed.

-- glen
 
K

Keith Thompson

Melzzzzz said:
On Sat, 1 Mar 2014 01:29:54 -0800 (PST)


Architecture/abi is that way.

The architecture/abi has nothing to do with it, at least not
directly. The language standard specifies that short arguments are
converted to int in this context. It might happen to be easier
to implement on some architectures (and architectures, ABIs, and
language standards tend to be designed to work well together),
but the compiler has to make it work regardless.

[temporarily posting through aioe.org due to problems with
eternal-september.org]
 
K

Keith Thompson

There's the `h' length modifier for shorts. Also `hh' for chars.

Good point, I momentarily forgot about those.

"%hd" still requires an int argument (possibly promoted from short),
but the value is *converted* to short before printing. I'm actually
not sure what it's good for. If the argument is already in the
range of type short, then it behaves identically to "%d"; if it's
outside that range, the result is implementation-defined (or, at
least in principle, it can raise an implementation-defined signal).

At least for "%hu" or "%hx", the behavior of the conversion is well
defined, but the behavior is still identical to "%u" or "%x" if the
argument is of type unsigned short. But I'm really not sure why the "h"
and "hh" length modifiers were added to the language. ("h" was added in
ANSI C89, "hh" in C99.)

[temporarily posting through aioe.org due to problems with
eternal-september.org]
 
J

Joe Pfeiffer

Keith Thompson said:
Good point, I momentarily forgot about those.

"%hd" still requires an int argument (possibly promoted from short),
but the value is *converted* to short before printing. I'm actually
not sure what it's good for. If the argument is already in the
range of type short, then it behaves identically to "%d"; if it's
outside that range, the result is implementation-defined (or, at
least in principle, it can raise an implementation-defined signal).

I always assumed that it was for consistency with scanf.
At least for "%hu" or "%hx", the behavior of the conversion is well
defined, but the behavior is still identical to "%u" or "%x" if the
argument is of type unsigned short. But I'm really not sure why the "h"
and "hh" length modifiers were added to the language. ("h" was added in
ANSI C89, "hh" in C99.)

[temporarily posting through aioe.org due to problems with
eternal-september.org]
 
Ad

Advertisements

K

Kaz Kylheku

I always assumed that it was for consistency with scanf.

There are blatant inconsistencies like:

printf("%f", double_var);

scanf("%f", &double_var); /* error */

scanf("%lf", &double_var); /* correct */

The consistency ship sailed long ago; printf and scanf missed it.
 
J

Joe keane

There are blatant inconsistencies like:

printf("%f", double_var);

scanf("%f", &double_var); /* error */

scanf("%lf", &double_var); /* correct */

First one should be "lf" ('long float').

When calling a varargs function it is legal to pass 'float' where
'double' is expected and vice versa.

A lot of rules for 'float' were changed, just not this one.

So there is your problem.

The 'printf' side has always been more tolerant; screwing up a 'scanf'
format will quickly crash, if you're lucky...
 
K

Kaz Kylheku

First one should be "lf" ('long float').

Nope. %f takes double.

"f,F A double argument representing a floating-point number is converted to
decimal notation in the style [−]ddd.ddd, where the number of digits after
the decimal-point character is equal to the precision specification."
(ISO/IEC 9899:1999)

Where is %g and %e in scanf? If printf were consistent with scanf,
%g would scan the general printed representation that may use exponents
or not, %f would scan only [-]ddd.ddd, and %e only exponentials.

%f in printf perhaps doesn't stand for "floating". It means "fixed digits":
in a sense the opposite of a "floating" decimal point with an exponent.
When calling a varargs function it is legal to pass 'float' where
'double' is expected and vice versa.

When calling a varargs function, it is impossible to pass a float;
any such expression widens to a double.
A lot of rules for 'float' were changed, just not this one.

Recently? In what way?
 
Ad

Advertisements

G

glen herrmannsfeldt

(snip)
Nope. %f takes double.
"f,F A double argument representing a floating-point number is converted to
decimal notation in the style [???]ddd.ddd, where the number of digits after
the decimal-point character is equal to the precision specification."
(ISO/IEC 9899:1999)
Where is %g and %e in scanf? If printf were consistent with scanf,
%g would scan the general printed representation that may use exponents
or not, %f would scan only [-]ddd.ddd, and %e only exponentials.

Having learned Fortran before C was even invented, where I presume
the E, F, ang G formats originated, on input all three are equivalent.
The width is used, and if not decimal point is supplied in the input
value, the .d part specified where the implied decimal point is.
%f in printf perhaps doesn't stand for "floating". It means "fixed digits":
in a sense the opposite of a "floating" decimal point with an exponent.

It is a litte more obvious in PL/I where fixed point does not imply
integer. (You specify the position of the binary or decimal point
when declaring a variable. The rules for arithmetic operators give
the position for expressions.) So, in PL/I F means fixed position
of the decimal point. Fortran has I for integer and F for fixed
decimal point in floating point (Fortran calls them REAL) values.

Note also that in PL/I REAL is the complement of COMPLEX, and FIXED
the complement of FLOAT.
When calling a varargs function, it is impossible to pass a float;
any such expression widens to a double.
Recently? In what way?

-- glen
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top