Printing fixed size integer types

S

Spoon

Hello everyone,

In my code, I use uint16_t (exactly 16-bit-wide unsigned integer type)
and I need to print their value in base 10.

http://www.opengroup.org/onlinepubs/009695399/basedefs/inttypes.h.html

As far as I understand, the recommended method is:

#include <inttypes.h>
#include <stdio.h>
int main(void)
{
uint16_t val = 1234;
printf("VALUE = %" PRIu16 "\n", val);
return 0;
}

Is this correct?

On my platform (Linux/IA-32/GCC/glibc) inttypes.h provides the
following definitions:

# define PRIu8 "u"
# define PRIu16 "u"
# define PRIu32 "u"
# define PRIu64 __PRI64_PREFIX "u"

I expected PRIu16 to be "hu". Is it "u" because, on my platform,
16-bit values are automatically promoted to 32-bit values, which
might make "u" and "hu" equivalent?

Regards.
 
A

ais523

Hello everyone,

In my code, I use uint16_t (exactly 16-bit-wide unsigned integer type)
and I need to print their value in base 10.

http://www.opengroup.org/onlinepubs/009695399/basedefs/inttypes.h.html

As far as I understand, the recommended method is:

#include <inttypes.h>
#include <stdio.h>
int main(void)
{
uint16_t val = 1234;
printf("VALUE = %" PRIu16 "\n", val);
return 0;

}

Is this correct?

On my platform (Linux/IA-32/GCC/glibc) inttypes.h provides the
following definitions:

# define PRIu8 "u"
# define PRIu16 "u"
# define PRIu32 "u"
# define PRIu64 __PRI64_PREFIX "u"

I expected PRIu16 to be "hu". Is it "u" because, on my platform,
16-bit values are automatically promoted to 32-bit values, which
might make "u" and "hu" equivalent?

Regards.

First, one relevant factor here is that printf takes a variable number
of arguments.

Looking at n1124.pdf (which I believe, but don't know for certain, is
the same as the C99 standard in this case):

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

These default argument promotions are defined in 6.5.2.2, 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.

Finally, the integer promotions are defined in section 6.3.1.1,
paragraph 2:

# If an int can represent all values of the original type, the value
is converted to an int;
# otherwise, it is converted to an unsigned int. These are called the
integer
# promotions. All other types are unchanged by the integer promotions.

(Some extra context is needed here; this applies not in all cases, but
in some predefined cases; the relevant one here is "An object or
expression with an integer type whose integer conversion rank is less
than or equal to the rank of int and unsigned int.")

I'm guessing that your system has a 32-bit int and a 16-bit short. In
this case, an int can represent all values a unsigned short can, and
an unsigned short has an integer conversion rank less than that of an
unsigned int, so the default argument promotions will promote the
unsigned short argument (a 'trailing argument') to a (signed) int
before the function is called, so printf will see an int whenever it's
given an unsigned short as its argument. I'm also guessing that in
this case, uint16_t is unsigned short, so your uint16_t argument will
be promoted to an int. Finally, section 7.15.1.1, paragraph 2, allows
as as an exception to the rule that the argument's actual type (int,
in this case) must be compatible with the type given when reading an
argument using va_arg:

# one type is a signed integer type, the other type is the
corresponding unsigned integer
# type, and the value is representable in both types

So your uint16_t is promoted to a (signed) int when you call printf;
printf reads this value as an unsigned int due to the %u specifier,
but it's allowed to do this because all values representable in a
uint16_t are representable as unsigned ints and unsigned int is the
corresponding unsigned integer type to int, and so the %u specifier is
correct. (The same thing would happen if you just passed an unsigned
short to printf directly; printf would have to use a signed or
unsigned int to read back its value, so %hu could be defined to do the
same as %u in all cases and still be standard-complaint, as far as I
can tell.)

Note that all this only happens because of the ... in printf's
prototype; if printf always took an unsigned short (or a uint16_t) as
an argument and its prototype said that, then it would get an unsigned
short as its parameter, not an int.

To answer your first question, placing PRIu16 into printf's format
string is exactly the right thing to do, as your header files take
care of all these complicated integer promotions for you, and that's
what the PRIu16 macro is for in the first place.
 
B

Ben Bacarisse

Spoon said:
In my code, I use uint16_t (exactly 16-bit-wide unsigned integer type)
and I need to print their value in base 10.
As far as I understand, the recommended method is:

#include <inttypes.h>
#include <stdio.h>
int main(void)
{
uint16_t val = 1234;
printf("VALUE = %" PRIu16 "\n", val);
return 0;
}

Is this correct?
Yes.

On my platform (Linux/IA-32/GCC/glibc) inttypes.h provides the
following definitions:

# define PRIu8 "u"
# define PRIu16 "u"
# define PRIu32 "u"
# define PRIu64 __PRI64_PREFIX "u"

I expected PRIu16 to be "hu". Is it "u" because, on my platform,
16-bit values are automatically promoted to 32-bit values, which
might make "u" and "hu" equivalent?

Yes. The "default argument promotions" are applied to the trailing
arguments of a printf call.
 
C

christian.bau

On my platform (Linux/IA-32/GCC/glibc) inttypes.h provides the
following definitions:

# define PRIu8 "u"
# define PRIu16 "u"
# define PRIu32 "u"
# define PRIu64 __PRI64_PREFIX "u"

I expected PRIu16 to be "hu". Is it "u" because, on my platform,
16-bit values are automatically promoted to 32-bit values, which
might make "u" and "hu" equivalent?

A value of type uint16_t would be printed correctly either way, since
it will always be promoted to unsigned int and will always be in the
range from 0 to 65535. The difference would be if you print a value
larger than 65535. If unsigned int is 16 bit, then this would be
undefined behaviour, no matter what format you use. If the value is of
type unsigned int, then "u" will print the actual value, while "hu"
will print the value after casting to unsigned short. It doesn't make
much difference, because there is a programmer's error anyway, but
printing the actual value seems better to me.
 
J

Jack Klein

A value of type uint16_t would be printed correctly either way, since
it will always be promoted to unsigned int and will always be in the
^^^^^^^^^^^^
range from 0 to 65535. The difference would be if you print a value
larger than 65535. If unsigned int is 16 bit, then this would be
undefined behaviour, no matter what format you use. If the value is of
type unsigned int, then "u" will print the actual value, while "hu"
will print the value after casting to unsigned short. It doesn't make
much difference, because there is a programmer's error anyway, but
printing the actual value seems better to me.

The part of your reply that I highlighted is incorrect. On the
platform the OP mentioned, where ints have 32 bits (and no padding)
and unsigned short is 16 bits, the value of an unsigned short will be
promoted to signed int by the default promotions, not to unsigned int.

The more correct conversion specifier for both PRIu8 and PRIu16 would
be "d", not "u", since both convert to signed int.

There is no undefined behavior, because somewhere in the standard (I
didn't find it in a quick look, and I'm not going to spend more time
on it now) it specifically states that signed and unsigned integer
types are compatible as parameters to (all or variadic) functions
provided that the value is within the common range of both.

--
Jack Klein
Home: http://JK-Technology.Com
FAQs for
comp.lang.c http://c-faq.com/
comp.lang.c++ http://www.parashift.com/c++-faq-lite/
alt.comp.lang.learn.c-c++
http://www.club.cc.cmu.edu/~ajo/docs/FAQ-acllc.html
 
P

pete

Jack Klein wrote:
There is no undefined behavior, because somewhere in the standard (I
didn't find it in a quick look, and I'm not going to spend more time
on it now) it specifically states that signed and unsigned integer
types are compatible as parameters to (all or variadic) functions
provided that the value is within the common range of both.

N869
6.2.5 Types

[#6] For each of the signed integer types, there is a
corresponding (but different) unsigned integer type
(designated with the keyword unsigned) that uses the same
amount of storage (including sign information) and has the
same alignment requirements.

[#9] The range of nonnegative values of a signed integer
type is a subrange of the corresponding unsigned integer
type, and the representation of the same value in each type
is the same.28)

28)The same representation and alignment requirements are
meant to imply interchangeability as arguments to
functions, return values from functions, and members of
unions.
 
C

Charlie Gordon

Jack Klein said:
The part of your reply that I highlighted is incorrect. On the
platform the OP mentioned, where ints have 32 bits (and no padding)
and unsigned short is 16 bits, the value of an unsigned short will be
promoted to signed int by the default promotions, not to unsigned int.

The more correct conversion specifier for both PRIu8 and PRIu16 would
be "d", not "u", since both convert to signed int.

There is no undefined behavior, because somewhere in the standard (I
didn't find it in a quick look, and I'm not going to spend more time
on it now) it specifically states that signed and unsigned integer
types are compatible as parameters to (all or variadic) functions
provided that the value is within the common range of both.

C99 6.2.5p9 is close to that:

The range of nonnegative values of a signed integer type is a subrange of
the
corresponding unsigned integer type, and the representation of the same
value in each
type is the same.31)

31) The same representation and alignment requirements are meant to imply
interchangeability as
arguments to functions, return values from functions, and members of unions.

But of course, the footnote might be non-normative.
 
D

David Thompson

The more correct conversion specifier for both PRIu8 and PRIu16 would
be "d", not "u", since both convert to signed int.

There is no undefined behavior, because somewhere in the standard (I
didn't find it in a quick look, and I'm not going to spend more time
on it now) it specifically states that signed and unsigned integer
types are compatible as parameters to (all or variadic) functions
provided that the value is within the common range of both.

Not quite specifically.

As already noted there is a nonnormative footnote that they are
'intended' to be interchangeable.

For calls to nonprototyped functions, there is a guarantee in
6.5.2.2p6. But printf is not nonprototyped.

For variadic arguments accessed by va_arg, there is a guarantee in
7.15.1p2. But std-lib routines like printf are not required to use
va_arg or even to be implemented in C at all.

It would take real perversity to make these required things work
without also making printf work, and I don't expect any actual
(non-DS9k) implementor to do so. But it isn't specifically required.

- formerly david.thompson1 || achar(64) || worldnet.att.net
 

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

Staff online

Members online

Forum statistics

Threads
473,770
Messages
2,569,583
Members
45,072
Latest member
trafficcone

Latest Threads

Top