Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to print types of unknown size like ino_t?

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:

  • I have to know whether the type I'm trying print is signed or unsigned.
  • I have to use a type cast that possibly enlarges my code.

Is there a better strategy?

like image 739
fuz Avatar asked Jul 19 '14 21:07

fuz


4 Answers

#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, or X conversion specifier applies to an intmax_t or uintmax_t argument; or that a following n conversion specifier applies to a pointer to an intmax_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.

like image 103
Keith Thompson Avatar answered Oct 17 '22 03:10

Keith Thompson


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 typedefd 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 *…)

like image 35
Arkku Avatar answered Oct 17 '22 05:10

Arkku


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:

  • All unsigned integer operations works modulo "N" for some positive value N, depending of the type. It implies that every result involving only unsigned integer types gives a nonnegative value.
  • To detect if a variable 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:

  • As Pascal Cuoq pointed out in his comment, the integer promotions has to be taken in accont for unsigned 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.

  • If the value is negative, obviously the type is not unsigned.
  • If the value N is positive then
    • if -N is positive, then N has unsigned type,
    • If -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.

like image 6
pablo1977 Avatar answered Oct 17 '22 03:10

pablo1977


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.

EDIT:

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

like image 4
Grzegorz Szpetkowski Avatar answered Oct 17 '22 04:10

Grzegorz Szpetkowski