Reputation: 93054
I often experience situations where I want to print with printf
the value of an integer type of implementation-defined size (like ino_t
or time_t
). Right now, I use a pattern like this for this:
#include <inttypes.h>
ino_t ino; /* variable of unknown size */
printf("%" PRIuMAX, (uintmax_t)ino);
This approach works so far but it has a couple of disadvantages:
Is there a better strategy?
Upvotes: 26
Views: 14873
Reputation: 263357
#include <inttypes.h>
ino_t ino; /* variable of unknown size */
/* ... */
printf("%" PRIuMAX, (uintmax_t)ino);
That will certainly work (with some provisos; see below), but I'd use:
printf("%ju", (uintmax_t)ino);
The j
length modifier
Specifies that a following
d
,i
,o
,u
,x
, orX
conversion specifier applies to anintmax_t
oruintmax_t
argument; or that a followingn
conversion specifier applies to a pointer to anintmax_t
argument.
There are also z
and t
modifiers for size_t
and ptrdiff_t
(and their corresponding signed/unsigned types), respectively.
And personally, I find the format string macros defined in <inttypes.h>
ugly and difficult to remember, which is why I prefer "%ju"
or "%jd"
.
As you mentioned, it's helpful to know whether the type (ino_t
in this case) is signed or unsigned. If you don't happen to know that, it's possible to figure it out:
#include <stdio.h>
#include <stdint.h>
#include <sys/types.h>
#define IS_SIGNED(type) ((type)-1 < (type)0)
#define DECIMAL_FORMAT(type) (IS_SIGNED(type) ? "%jd" : "%ju")
#define CONVERT_TO_MAX(type, value) \
(IS_SIGNED(type) ? (intmax_t)(value) : (uintmax_t)(value))
#define PRINT_VALUE(type, value) \
(printf(DECIMAL_FORMAT(type), CONVERT_TO_MAX(type, (value))))
int main(void) {
ino_t ino = 42;
PRINT_VALUE(ino_t, ino);
putchar('\n');
}
though that may be overkill. If you're sure the type is narrower than 64 bits, you can convert the value to intmax_t
, and the value will be preserved. Or you can use uintmax_t
and get well-defined results for all values, though printing -1
as 18446744073709551615
(264-1) may be a bit confusing.
All of this works only if your C implementation supports <stdint.h>
and the j
length modifier for printf
-- i.e., if it supports C99. Not all compilers do so (coughMicrosoftcough). For C90, the widest integer types are long
and unsigned long
, and you can convert to those and use "%ld"
and/or "%lu"
. You can theoretically test for C99 compliance using the __STDC_VERSION__
predefined macro -- though some pre-C99 compilers might still support types wider than long
and unsigned long
as an extension.
Upvotes: 12
Reputation: 42149
Using C11 type generic macros, it is possible to construct the format string at compile time, e.g.:
#include <inttypes.h>
#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#define PRI3(B,X,A) _Generic((X), \
unsigned char: B"%hhu"A, \
unsigned short: B"%hu"A, \
unsigned int: B"%u"A, \
unsigned long: B"%lu"A, \
unsigned long long: B"%llu"A, \
signed char: B"%hhd"A, \
short: B"%hd"A, \
int: B"%d"A, \
long: B"%ld"A, \
long long: B"%lld"A)
#define PRI(X) PRI3("",(X),"")
#define PRIFMT(B,X,A) PRI3(B,(X),A),(X)
int main () {
signed char sc = SCHAR_MIN;
unsigned char uc = UCHAR_MAX;
short ss = SHRT_MIN;
unsigned short us = USHRT_MAX;
int si = INT_MIN;
unsigned ui = UINT_MAX;
long sl = LONG_MIN;
unsigned long ul = ULONG_MAX;
long long sll = LLONG_MIN;
unsigned long long ull = ULLONG_MAX;
size_t z = SIZE_MAX;
intmax_t sj = INTMAX_MIN;
uintmax_t uj = UINTMAX_MAX;
(void) printf(PRIFMT("signed char : ", sc, "\n"));
(void) printf(PRIFMT("unsigned char : ", uc, "\n"));
(void) printf(PRIFMT("short : ", ss, "\n"));
(void) printf(PRIFMT("unsigned short : ", us, "\n"));
(void) printf(PRIFMT("int : ", si, "\n"));
(void) printf(PRIFMT("unsigned int : ", ui, "\n"));
(void) printf(PRIFMT("long : ", sl, "\n"));
(void) printf(PRIFMT("unsigned long : ", ul, "\n"));
(void) printf(PRIFMT("long long : ", sll, "\n"));
(void) printf(PRIFMT("unsigned long long: ", ull, "\n"));
(void) printf(PRIFMT("size_t : ", z, "\n"));
(void) printf(PRIFMT("intmax_t : ", sj, "\n"));
(void) printf(PRIFMT("uintmax_t : ", uj, "\n"));
}
There is a potential problem, though: if there are types distinct from those listed (i.e., other than signed
and unsigned
versions of char
, short
, int
, long
, and long long
), this does not work for those types. It is also not possible to add types such as size_t
and intmax_t
to the type generic macro "just in case", because it will cause an error if they are typedef
d from one of the already listed types. I've left the default
case unspecified so that the macro generates a compile-time error when no matching type is found.
However, as seen in the example program, size_t
and intmax_t
work just fine on platforms where they are the same as one of the listed types (e.g., same as long
). Similarly there is no issue if, e.g., long
and long long
, or long
and int
, are the same type. But a safer version might be to just cast to intmax_t
or uintmax_t
according to signedness (as seen in other answers), and make a type generic macro with only those options…
A cosmetic problem is that the type generic macro expands with parentheses around the string literal, preventing concatenation with adjacent string literals in the usual way. This prevents things like:
(void) printf("var = " PRI(var) "\n", var); // does not work!
Hence the PRIFMT
macro with prefix and suffix included for the common case of printing a single variable:
(void) printf(PRIFMT("var = ", var, "\n"));
(Note that it would be simple to expand this macro with the non-integer types supported by printf
, e.g., double
, char *
…)
Upvotes: 7
Reputation: 4433
The "size" of an integer type is not relevant here, but its range of values.
As apparently you tried, yet, it is possible to cast to uintmax_t
and intmax_t
to easily solve any ambiguity in the printf()
call.
The issue of the signed or unsigned types can be solved in an easy way:
x
has a signed or an unsigned type, it is enough to verify if x
and -x
are both nonnegative values. For example:
if ( (x>=0) && (-x>=0) )
printf("x has unsigned type");
else
printf("x has signed type");
Now, we can write some macros:
(Edited: the name and expression of the macro have changed)
#include <inttypes.h>
#include <limits.h>
#define fits_unsigned_type(N) ( (N >= 0) && ( (-(N) >= 0) || ((N) <= INT_MAX) ) )
#define smartinteger_printf(N) \
(fits_unsigned_type(N)? printf("%ju",(uintmax_t)(N)): printf("%jd",(intmax_t) (N)) )
// ....
ino_t x = -3;
printf("The value is: ");
smartinteger_printf(x);
//.....
Note: The signed or unsigned character of a variable is not well detected by the macro above when the value is 0. But in this case everything works well, because 0 has the same bit representation in signed or unsigned types.
The first macro can be used to detect if the underlying type of an arithmetical object has unsgined type or not.
This result is used in the second macro to choose the way in that the object is printed on screen.
1st REEDITION:
char
and short
values fiiting in the range of int
. This is equivalent to ask if the value is in the rango 0 to INT_MAX
. So I have changed the name of the macro to fits_signed_type
.
Also, I have modified the macro to take in account the positive int
values.
The macro fits_unsigned_type
can tell if an object has unsigned integer type or not in most cases.
unsigned
. -N
is positive, then N has unsigned
type, -N
is negative, but N is in the range 0 to INT_MAX
, then the type of N
could be signed
or unsigned
, but it would fit in the range of positive values ofint
, which fits in the range of uintmax_t
. 2nd REEDITION:
Ir seems that there are here to approaches to solve the same problem. My approach takes in account the range of values and integer promotion rules to produce the correct printed value with printf()
. On the other hand, Grzegorz Szpetkowski's approach determines the signed character of a type in straight form. I like both.
Upvotes: 6
Reputation: 37944
Since your are already using C99 header, there is a possibility to use exact width format specifier depending on sizeof(T)
and signed/unsigned check. This however has to done after preprocessing phase (so sadly ##
operator cannot be used here to construct PRI token). Here is an idea:
#include <inttypes.h>
#define IS_SIGNED(T) (((T)-1) < 0) /* determines if integer type is signed */
...
const char *fs = NULL;
size_t bytes = sizeof(T);
if (IS_SIGNED(T))
switch (bytes) {
case 1: fs = PRId8; break;
case 2: fs = PRId16; break;
case 4: fs = PRId32; break;
case 8: fs = PRId64; break;
}
else
switch (bytes) {
case 1: fs = PRIu8; break;
case 2: fs = PRIu16; break;
case 4: fs = PRIu32; break;
case 8: fs = PRIu64; break;
}
With this method cast is not needed anymore, however format string has to be manually constructed before passing it to printf
(i.e. no automatic string concatenation). Here is some working example:
#include <stdio.h>
#include <inttypes.h>
#define IS_SIGNED(T) (((T)-1) < 0)
/* using GCC extension: Statement Expr */
#define FMT_CREATE(T) ({ \
const char *fs = NULL; \
size_t bytes = sizeof(ino_t); \
\
if (IS_SIGNED(T)) \
switch (bytes) { \
case 1: fs = "%" PRId8; break; \
case 2: fs = "%" PRId16; break; \
case 4: fs = "%" PRId32; break; \
case 8: fs = "%" PRId64; break; \
} \
else \
switch (bytes) { \
case 1: fs = "%" PRIu8; break; \
case 2: fs = "%" PRIu16; break; \
case 4: fs = "%" PRIu32; break; \
case 8: fs = "%" PRIu64; break; \
} \
fs; \
})
int main(void) {
ino_t ino = 32;
printf(FMT_CREATE(ino_t), ino); putchar('\n');
return 0;
}
Note this requires some little trickery of Statement Expr, but there might be some other way (this is the "price" to be generic) as well.
Here is second version, that doesn't require specific compiler extension (don't worry I can't read it too) using function-like macro:
#include <stdio.h>
#include <inttypes.h>
#define IS_SIGNED(T) (((T)-1) < 0)
#define S(T) (sizeof(T))
#define FMT_CREATE(T) \
(IS_SIGNED(T) \
? (S(T)==1?"%"PRId8:S(T)==2?"%"PRId16:S(T)==4?"%"PRId32:"%"PRId64) \
: (S(T)==1?"%"PRIu8:S(T)==2?"%"PRIu16:S(T)==4?"%"PRIu32:"%"PRIu64))
int main(void)
{
ino_t ino = 32;
printf(FMT_CREATE(ino_t), ino);
putchar('\n');
return 0;
}
Note that conditional operator has left assiociativity (thus it evalutes from left to right as intended).
Upvotes: 4