Linux Fu: Customizing Printf

When it comes to programming in C and, sometimes, C++, the printf function is a jack-of-all-trades. It does a nice job of quickly writing output, but it can also do surprisingly intricate formatting. For debugging, it is a quick way to dump some data. But what if you have data that printf can’t format? Sure, you can just write a function to pick things apart into things printf knows about. But if you are using the GNU C library, you can also extend printf to use custom specifications. It isn’t that hard, and it makes using custom data types easier.

An Example

Suppose you are writing a program that studies coin flips. Even numbers are considered tails, and odd numbers are heads. Of course, you could just print out the number or even mask off the least significant bit and print that. But what fun is that?

Here’s a very simple example of using our new printf specifier “%H”:

printf("%H %H %H %H\n",1,2,3,4);
printf("%1H %1H\n",0,1);

When you have a width specification of 1 (like you do in the first line) the output will be H or T. If you have anything else, the output will be HEADS or TAILS.

Easy!

But first, we need to add the %H specifier, and it’s easy. It would be even more straightforward, but the system is very flexible, so there are a few hurdles. The key lies in the printf.h header. This defines several functions that allow you to bend printf to your will.

You have to provide two functions. The first takes an output stream, a structure of information, and a void * to the current printf argument list. The function’s job is to grab the argument and output to the stream. The information structure has things like the field width and precision, which you can use or not, as you see fit. The function returns the length of the output.

The second function receives the same information structure and several arguments to process. It also receives two arrays. This function is tasked to tell the printf code how many arguments of what type the specifier needs. This function is usually simple. You probably only take one argument of a known type, so you put a predefined constant in the first array, and you are done. However, if you want to do something more complicated, it is a bit more work.

A Little Harder

If you need to take multiple items off the stream (for example, you are printing complex numbers), the second function might fill in more than one array item, and it will also return the count. You can also define custom types that you have to register (using register_printf_type) and then you have to fill in a size in the size array, too.

However, these are unusual. Most of the time, you just need to enter a data type and return 1. Here’s an example:


static int print_coin_arginfo( const struct print_info *info,
    size_t n, int argtype[], int size[])
{
   if (n>0) argtypes[0]=PA_INT // there needs to be at least an integer waiting for us
   return 1; // only one thing to read
}

Nothing to it!

The Main Event

The primary function doesn’t have to be hard, although maybe what you want to do is difficult — that can’t be helped. You do have to cast the incoming pointer to the correct type. Of course, if print_coin_arginfo returned more than one item (which it won’t), you would have to process each argument.

The only other complexity is to handle the data in the information structure if you want to. The main things of interest are the prec and width members of the structure. But you can also find modifiers like the “l” flag (as in %ld) and other flags. You can use or ignore these. In our case, we care about the width since we will print H or T if the width is 1. We also want to know about the width for formatting and if we are left justified. So, the following would all be legitimate:

%H - Just do it
%1H - Print H or T
%20H - Print with a 20-character field (right justified)
%-20H - Sam as %20 but left-justified

Here’s the function:


static int print_coin (FILE *stream,
   const struct printf_info *info,
   const void *const *args)
{
  int headstails;
  char *buffer;
  int len;

/* figure out our string */
  headstails = *((const int *) (args[0]));
  if (info->width!=1)
    {
    buffer=(headstails&1)?"HEADS":"TAILS";
    }
  else
    {
    buffer=(headstails&1)?"H":"T";
    }
/* Pad to the minimum field width and print to the stream. */
  len = fprintf (stream, "%*s",
      (info->left ? -info->width : info->width),
       buffer);
return len;
}

We cheat and use fprintf, but that’s allowable. Obviously, it wouldn’t be a good idea to use %H in that printf!

Gothchas

You can download a complete copy from a Gist. If you try the code as it is —  — you will get warnings because the compiler is smart enough to know about printf, but not smart enough to know you’ve messed with printf. You can turn off the warning from the command line or with a pragma. Interestingly, using -Wno-format seems to turn off all the warnings. But if you will need to turn off both warnings with the pragma method:

#pragma GCC diagnostic ignored "-Wformat"
#pragma GCC diagnostic ignored "-Wformat-extra-args"

Of course, the other problem is that this is very specific to the GNU library. You are making your code very non-portable by doing this. If you care, then don’t do it! If you don’t — maybe you are just using it while debugging, or you know you won’t have to move your code — then this is a nice way to extend the library. You also have to worry if a future version of the library will use your format specifier letter. Typically, standard ones are lowercase, but sometimes, a standard one uses both upper and lower (for example, %x and %X to control the output case of hex number). You have been warned!

Linux has a long history of being able to customize things that don’t seem customizable. The file system, for example. Or even sharing your WiFi using your WiFi.



Linux Fu: Customizing Printf
Source: Manila Flash Report

Post a Comment

0 Comments