简体   繁体   中英

Why does printf's hh and h length modifiers exist?

In variadic functions, default argument promotions occur.

6.5.2.2.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 . [...]

6.5.2.2.7 [...] 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.

Therefore,

signed char c = 123;
int         i = 123;
float       f = 123;
double      d = 123;

printf("%d\n", i);   // ok
printf("%d\n", c);   // ok, even though %d expects int.
printf("%f\n", d);   // ok
printf("%f\n", f);   // ok, even though %f expects double.

So why is there a printf length modifier for char ( hh ) and short ( h )?


Section number refer to N2176.

Consider this example:

#include <stdio.h>
int main(void)
{
    unsigned short x = 32770;
    printf("%d\n", x) ;  // (1)
    printf("%u\n", x) ;  // (2)
}

On a typical 16-bit implementation, the default argument promotions take unsigned short to unsigned int , whereas on a typical 32-bit implementation, unsigned short becomes int .

So on the 16-bit system (1) is UB and (2) is correct, but on the 32-bit system, (1) is correct and (2) it can be debated whether correct or UB.

Using %hu for printing x works on all systems and you don't have to think about these issues.

A similar example could be constructed for char on systems with sizeof(int) == 1 .

It's for backwards compatibility.

In a draft version of the C89 standard, printing a signed int , short or char with the %x format specifier is not undefined behavior :

d, i, o, u, x, X The int argument is converted to signed decimal ( d or i ), unsigned octal ( o ), unsigned decimal ( u ), or unsigned hexadecimal notation ( x or X ); the letters abcdef are used for x conversion and the letters ABCDEF for X conversion. The precision specifies the minimum number of digits to appear; if the value being converted can be represented in fewer digits, it will be expanded with leading zeros. The default precision is 1. The result of converting a zero value with an explicit precision of zero is no characters.

This seems to document that pre-standardized C, using format specifiers such as %x for signed values was an existing practice, thus a preexisting code base using h and hh length modifiers likely existed.

Without the h and hh length modifiers, signed char values with a bit pattern 0xFF would be printed on a 32-bit- int system as 0xFFFFFFFF if printed with a simple %X format specifier.

Regarding the hh specifier specifically, it was explicitly added in C99 in order to utilize printing of all the default fixed size types from stdint.h / inttypes.h . C99 mades the types int_leastn_t from 8 to 64, mandatory, so there was a need for corresponding format specifiers.

From the C99 rationale 5.10, §7.19.6.1 ( fprintf ):

The %hh and %ll length modifiers were added in C99 (see §7.19.6.2).

§7.19.6.2 ( fscanf ):

A new feature of C99 : The hh and ll length modifiers were added in C99. ll supports the new long long int type. hh adds the ability to treat character types the same as all other integer types; this can be useful in implementing macros such as SCNd8 in <inttypes.h> (see 7.18).

Before C99, there was just d , h and l for printing integer types. In C99, a conventional implementation could for example define inttypes.h specifiers as:

#define SCNi8   hh
#define SCNi16   h
#define SCNi32   d
#define SCNi64  ll

And now the default argument promotions becomes the head ache of the printf / scanf implementation, rather than the inttypes.h implementation.

They are not there for printf() usage, but for scanf() to be able to use references to short integers and char integers. For uniformity and completeness, they are accepted for printf() functions, but they are undistinguisable, as the vaarg parameters of printf() are promoted to int for all parameters that are of types short and char integer values. So they are equivalent in printf() but not in scanf() and friends.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM