cprintf

Why is the octal "0" prefix displayed despite the explicit 0 precision when printing 0 integer with # flag?


Why is the prefix written with octal when using both the # flag and explicit 0 precision for unsigned int conversions? Is it normal?

I've noticed this and it feels like a bug (I'm not claiming it is).

However I'd like to understand how it works because it's making very little sense to me. Is there a rationale behind?

My best guess so far is that the prefix is handled after the precision. Since an empty output (which is what you get with printing 0 with an explicit precision of 0) doesn't start with "0" the "0" prefix is added. But I would've expected the prefix to be included in the concept of "output" and therefore I'd have expected it to not be displayed.


MRE:

/* main.c */
#include <stdio.h>
int main()
{
    printf("%%u conv: [%#.0u]\n", 0);
    printf("%%o conv: [%#.0o]\n", 0);
    return (0);
}

Output:

> gcc -o test main.c && ./test
%u conv: []
%o conv: [0]

Highlights from man 3 printf:

The value should be converted to an "alternate form". For o conversions, the first character of the output string is made zero (by prefixing a 0 if it was not zero already). For x and X conversions, a nonzero result has the string "0x" (or "0X" for X conversions) prepended to it. [...]

An optional precision, in the form of a period ('.') followed by an optional decimal digit string. [...] This gives the minimum number of digits to appear for d, i, o, u, x, and X conversions [...].

[...] The precision, if any, gives the minimum number of digits that must appear; if the converted value requires fewer digits, it is padded on the left with zeros. The default precision is 1. When 0 is printed with an explicit precision 0, the output is empty.


Solution

  • This is normal, and specified by the C standard:

    #
    The result is converted to an ''alternative form''. For o conversion, it increases the precision, if and only if necessary, to force the first digit of the result to be a zero (if the value and precision are both 0, a single 0 is printed). For x (or X) conversion, a nonzero result has 0x (or 0X) prefixed to it. ...

    For o conversion, # forces a leading zero even if the value and precision are both 0. For x conversion, the leading 0x or 0X is only prefixed to nonzero values.

    You can also see that the man page you quoted says something similar: # with o forces a leading zero no matter what, while # with x or X only adds a prefix to nonzero values.

    Note that o, x, and X are the only integer conversions for which the behavior of # is defined. Using # with u is undefined behavior.