How to use printf to display off_t, nlink_t, size_t and other special types?
Asked Answered
D

2

28

In my program, I stat the files they want and send the data over. The fields of a stat struct are all special types:

struct stat {
  dev_t     st_dev;     /* ID of device containing file */
  ino_t     st_ino;     /* inode number */
  mode_t    st_mode;    /* protection */
  nlink_t   st_nlink;   /* number of hard links */
  uid_t     st_uid;     /* user ID of owner */
  gid_t     st_gid;     /* group ID of owner */
  dev_t     st_rdev;    /* device ID (if special file) */
  off_t     st_size;    /* total size, in bytes */
  blksize_t st_blksize; /* blocksize for file system I/O */
  blkcnt_t  st_blocks;  /* number of 512B blocks allocated */
  time_t    st_atime;   /* time of last access */
  time_t    st_mtime;   /* time of last modification */
  time_t    st_ctime;   /* time of last status change */
};

The relevant code for my question follows:

len = snprintf( statbuf, STAT_BUFFER_SIZE,
  "%crwxrwxrwx %lu %u %u %lld %s %s\r\n",
  S_ISDIR( filestats.st_mode ) ? 'd' : '-',
  (unsigned long ) filestats.st_nlink,
  filestats.st_uid,
  filestats.st_gid,
  (unsigned long long ) filestats.st_size,
  date,
  filename);

How do I print these types in a portable and efficient way? At first I did it without casts by guessing the correct format specifiers. Apart from being an annoying programming habit, this also meant my code wouldn't work on a 32 bit system. Now with the casts it seems to work, but on how many platforms?

Drachm answered 9/9, 2009 at 19:15 Comment(2)
Specific for clock_t : #1083642Redletter
For __uint128: #11656741Redletter
T
29

There isn't a fully portable way to do it, and it is a nuisance.

C99 provides a mechanism for built-in types like size_t with the %zu notation (and there are some extra, similar qualifiers).

It also provides the <inttypes.h> header with macros such as PRIX32 to define the correct qualifier for printing a 32-bit hexadecimal constant (in this case):

printf("32-bit integer: 0x%08" PRIX32 "\n", var_of_type_int32_t);

For the system-defined types (such as those defined by POSIX), AFAIK, there is no good way to handle them. So, what I do is take a flying guess at a 'safe' conversion and then print accordingly, including the cast, which is what you illustrate in the question. It is frustrating, but there is no better way that I know of. In case of doubt, and using C99, then conversion to 'unsigned long long' is pretty good; there could be a case for using a cast to uintmax_t and PRIXMAX or equivalent.

Or, as FUZxxl reminded me, you can use the modifier j to indicate a 'max' integer type. For example:

printf("Maximal integer: 0x%08jX\n", (uintmax_t)var_of_type_without_format_letter);
Tina answered 9/9, 2009 at 19:27 Comment(6)
Couldn't have said it better. Using (u)intmax_t and PRI(d|u)MAX (or equivalent) may be the best solution for most problematic system-defined types.Platinic
I already knew those macro's but they looked so ugly to me. Thanks for the advice.Drachm
They're ugly, but they're the nearest thing to portable that I know of.Tina
btw, the format specifier for size_t should be %zu as size_t is an unsigned type...Cog
Converting it yourself with e.g. % (uid_t) 10 and /= (uid_t) 10 is fully portable.Cos
You might want to add that for intmax_t, the length modifier j exists, so you can use %ju to print an uintmax_t without using the bulky inttypes.h macros.Cyder
P
1

How to use printf to display off_t, nlink_t, size_t and other special types?

  1. When the type has a matching print specifier, use that

    printf("Size is %zu\n", object_with_type_size_t);
    
  2. When the type may lack a matching print specifier, yet the sign-ness is known, and is not an extended integer type, cast to the widest integer type.

    #include <inttypes.h>
    printf("PID is %jd\n", (intmax_t) object_with_type_pid_t);
    printf("INO is %ju\n", (uintmax_t) object_with_type_ino_t);
    
  3. When the type may lack a matching print specifier, the sign-ness is unknown, and is not an extended integer type, cast to a widest integer type. nlink_t sign-ness not specified. Use uintmax_t for portability. Use intmax_t for informative-ness. Or use crafted code.

    printf("link is %ju\n", (uintmax_t) object_with_type_nlink_t);
    
  4. When the integer type is an extended integer type, off_t are extended signed integral types, our options become trickier. We could cast to (u)intmax_t like above and potentially truncate the value. We could cast to a floating point type and potentially lose precision. We could create crafted code.

    printf("link is %jd\n", (intmax_t) object_with_type_off_t );
    // or 
    printf("link is %.0Lf\n", (long double) object_with_type_off_t );
    // or 
    
    // off_t is a signed extended integer type
    #define OFF_DECIMAL_SIZE ((sizeof(off_t) * CHAR_BIT - 1)*28/93 + 3)
    char buf[OFF_DECIMAL_SIZE];
    printf("link is %s\n", prt_off(buf, object_with_type_off_t));
    

Supporting code

// off_t is a signed extended integer type
#define OFF_DECIMAL_STR_SIZE ((sizeof(off_t) * CHAR_BIT - 1)*28/93 + 3)

char* prt_off(char *dest, off_t off) {
  char buffer[OFF_DECIMAL_STR_SIZE];

  // Start filling from the end
  char *p = &buffer[sizeof buffer - 1];
  *p = '\0';

  // Work with negative values to well handle the min value
  off_t an = off < 0 ? off : -off;

  do {
    *(--p) = (char) ('0' - an % 10);
    an /= 10;
  } while (an);

  if (off < 0) {
    *(--p) = '-';
  }

  size_t size_used = &buffer[sizeof(buffer)] - p;
  return memcpy(dest, p, size_used);
}
Photocompose answered 30/5, 2022 at 11:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.