9

If I want a program to have multiple text output formats, I could do something like this:

const char *fmtDefault = "%u x %s ($%.2f each)\n";
const char *fmtMultiLine = "Qty: %3u\nItem: %s\nPrice per item: $%.2f\n\n";
const char *fmtCSV = "%u,%s,%.2f\n";

const char *fmt;
switch (which_format) {
    case 1: fmt = fmtMultiLine; break;
    case 2: fmt = fmtCSV; break;
    default: fmt = fmtDefault;
}

printf(fmt, quantity, item_description, price);

Since the price is specified last, I could also add one that doesn't list prices:

const char *fmtNoPrices = "%u x %s\n";

But what if I want to omit the quantity instead? If I did this:

const char *fmtNoQuantity = "The price of %s is $%.2f each.\n";

then undefined behavior (most likely a segfault) will occur rather than what I want. This is because it will treat the first parameter as a pointer to a string, even though it's actually an unsigned int. This unsigned int will most likely point to something other than valid string data, or (much more likely, especially if you're not buying hundreds of millions of the same item) an invalid memory location, resulting in a segmentation fault.

What I want to know is if there's a code I can put somewhere (%Z in this example) to tell it to skip that parameter, like this:

const char *fmtNoQuantity = "%ZThe price of %s is $%.2f each.";
flarn2006
  • 1,787
  • 15
  • 37
  • 1
    scanf() can use asterisks, but IIRC printf() cannot. I've also tried using `.0` precision specifiers, but this appears to work only on strings (`%.0s` will display nothing, but possibly still dereference the pointer if not null) – Medinoc May 23 '13 at 08:13
  • You should be using separate sets of calls with separate argument lists, I think. Doing otherwise makes internationalization (I18N) much harder. – Jonathan Leffler May 27 '13 at 04:00

2 Answers2

7

For %s values, there is a “null” printf() code: %.0s.

You could reach a general solution via:

When possible, re-arrange so that non-%s values are last, and then under specify the format string.

My favorite for you is to have 3 separate printf() calls, one for each value using its own format. When the value is not needed, simply supply a format string with no specifiers.

const char * Format1q   = "";
const char * Format1id  = "The price of %s";
const char * Format1p   = " is $%.2f each.\n";
...
printf(Format1q,  quantity); 
printf(Format1id, item_description);
printf(Format1p,  price);

Weird solutions:

For other values that are the same size you could attempt the Undefined Behavior of also using %.0s. (worked with some samples in gcc 4.5.3, who knows in other compilers or the future.)

For other values that are the N x the same size as a pointer size you could attempt the Undefined Behavior of also using %.0s N times. (worked with some samples in gcc 4.5.3, who knows in other compilers or the future.)

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • 1
    Beware internationalization (I18N). The sentence fragments that combine well in English may be a disaster in translation. – Jonathan Leffler May 27 '13 at 03:59
  • Agree about internationalization issues. Also, given the fixed order of values, we have it might be better to re-arrange the values far a different culture and that is beyond the presented solutions. – chux - Reinstate Monica May 27 '13 at 04:06
  • 1
    The `%1$s` notations can reorder (and even reuse) parameters, but you have to use every parameter from 1..N to be safe (because different types need `printf()` et al to use different amounts of space on the stack. For example, a `double` needs 8 bytes, where `int` typically only needs 4 bytes. So, if you have an `int` and a `double` on the stack, `printf()` has to be told how to advance the `va_list` that underlies all this stuff. That doesn't stop attackers using format string attacks with missing numbers in the `n$` numbers, but they aren't as fussy as long as it works for them. – Jonathan Leffler May 27 '13 at 04:13
1

I actually figured this out on my own while looking something up for my question. You can prepend a parameter number, followed by a $ to the format code, after the %. So it would be like this:

const char *fmtNoQuantity = "The price of %2$s is $%3$.2f each.";

That is, the string would use the 2nd parameter, and the float would use the 3rd parameter. Note, however, that this is a POSIX extension, not a standard feature of C.

A better method would probably be to define a custom printing function. Something like this:


typedef enum {fmtDefault, fmtMultiLine, fmtCSV, fmtNoPrices, fmtNoQuantity} fmt_id;

void print_record(fmt_id fmt, unsigned int qty, const char *item, float price)
{
    switch (fmt) {
    case fmtMultiLine:
        printf("Qty: %3u\n", qty);
        printf("Item: %s\n", item);
        printf("Price per item: $%.2f\n\n", price);
        break;
    case fmtCSV:
        printf("%u,%s,%.2f\n", qty, item, price);
        break;
    case fmtNoPrices:
        printf("%u x %s\n", qty, item);
        break;
    case fmtNoQuantity:
        printf("The price of %s is $%.2f each.\n", item, price);
        break;
    default:
        printf("%u x %s ($%.2f each)\n", qty, item, price);
        break;
    }
}
flarn2006
  • 1,787
  • 15
  • 37
  • 2
    A problem with this extension is that referencing a parameter without referencing all those that precede it causes an undefined behavior because the `printf` function doesn't know the size of these parameters. And there is no "skip this parameter" format that I know of, so this can't even be used to work around that problem... – Medinoc May 23 '13 at 08:07