From: Showta Ishizaki Subject: printf(3): "%#.0o" of 0 prints nothing instead of "0" To: tech@openbsd.org Cc: zakinko@snowrabbit.org Date: Fri, 11 Sep 2026 03:29:15 +0900 For the o conversion the # flag asks for a leading zero, and C11 7.21.6.1p6 says 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)". vfprintf.c gets to the digit loop with if (_umax != 0 || prec != 0) { so a zero with an explicit precision of zero produces no digits at all, and the "handle octal leading 0" step below never runs: printf("[%#.0o]\n", 0); [] should be [0] printf("[%#o]\n", 0); [0] printf("[%.0o]\n", 0); [] right, no # flag printf("[%#.0o]\n", 8); [010] glibc, FreeBSD and NetBSD print [0] for the first one; FreeBSD has "|| (flags & ALT && base == 8)" at the same place. abseil's str_format test compares against the C library and caught it on OpenBSD. The diff adds the same condition to vfprintf.c and vfwprintf.c. I rebuilt lib/libc from the 7.9 source with it and ran the lines above against the new libc.so.103.0 through LD_LIBRARY_PATH: the first prints [0], the other three are unchanged, and wprintf(3) behaves the same. Not built on -current, though the diff applies to it unchanged. Index: lib/libc/stdio/vfprintf.c --- lib/libc/stdio/vfprintf.c.orig +++ lib/libc/stdio/vfprintf.c @@ -923,9 +923,12 @@ * ``The result of converting a zero value with an * explicit precision of zero is no characters.'' * -- ANSI X3J11 + * + * The # flag overrides that for octal: it asks for a + * leading zero, so "%#.0o" of 0 is "0", not "". */ cp = buf + BUF; - if (_umax != 0 || prec != 0) { + if (_umax != 0 || prec != 0 || (flags & ALT && base == OCT)) { /* * Unsigned mod is hard, and unsigned mod * by a constant is easier than that by Index: lib/libc/stdio/vfwprintf.c --- lib/libc/stdio/vfwprintf.c.orig +++ lib/libc/stdio/vfwprintf.c @@ -901,9 +901,12 @@ * ``The result of converting a zero value with an * explicit precision of zero is no characters.'' * -- ANSI X3J11 + * + * The # flag overrides that for octal: it asks for a + * leading zero, so "%#.0o" of 0 is "0", not "". */ cp = buf + BUF; - if (_umax != 0 || prec != 0) { + if (_umax != 0 || prec != 0 || (flags & ALT && base == OCT)) { /* * Unsigned mod is hard, and unsigned mod * by a constant is easier than that by