为什么 printf 无法正确处理标志、字段宽度和精度?

Why printf is not able to handle flags, field width and precisions properly?

我正在尝试发现 printf 的所有功能,我已经试过了:

printf("Test:%+*0d", 10, 20);

打印

Test:%+100d

我首先使用标志 +,然后使用宽度 *,然后重新使用标志 0

为什么会这样输出?我故意以 糟糕的 方式使用 printf(),但我想知道为什么它显示数字 100?

这是因为,您向编译器提供了语法上的废话,因此它可以为所欲为。相关阅读,undefined behavior.

在启用警告的情况下编译您的代码,它会告诉您类似

的信息

warning: unknown conversion type character ‘0’ in format [-Wformat=]
printf("Test:%+*0d", 10, 20);
^

为了正确,该语句应该是

  • printf("Test:%+*.0d", 10, 20); // note the '.'

    其中,0用作精度

    相关,引用 C11,章节 §7.21.6.1,(强调我的

    An optional precision that gives the minimum number of digits to appear for the d, i, o, u, x, and X conversions, the number of digits to appear after the decimal-point character for a, A, e, E, f, and F conversions, the maximum number of significant digits for the g and G conversions, or the maximum number of bytes to be written for s conversions. The precision takes the form of a period (.) followed either by an asterisk * (described later) or by an optional decimal integer; if only the period is specified, the precision is taken as zero. If a precision appears with any other conversion specifier, the behavior is undefined.

  • printf("Test:%+0*d", 10, 20);

    其中,0 用作 标志。根据语法,all 标志应该一起出现,在任何其他转换规范条目之前,您不能只将它放在转换规范中的 anywhere 和希望编译器遵循您的意图

    再次引用,(和我的重点

    Each conversion specification is introduced by the character %. After the %, the following appear in sequence:

    • Zero or more flags (in any order) [...]
    • An optional minimum field width [...]
    • An optional precision [...]
    • An optional length modifier [...]
    • A conversion specifier [....]

; an important notion is that of undefined behavior, which is tricky. Be sure to read Lattner's blog: What Every C Programmer Should Know About Undefined Behavior. See also this 的补充。

因此,故意(或可能取决于)代码中的某些未定义行为是故意的不当行为。不要那样做。在极少数情况下你想这样做(我看不到),请记录下来并在评论中证明你自己。

请注意,如果 printf 确实由 C 标准库实现,它可以(通常 )由编译器(使用 GCC 和 GNU libc,这种魔法可能会在内部使用 __builtin_printf)

C99 和 C11 标准部分 指定了 printf 的行为,但确实留下了一些未定义的行为案例以简化实施。您不太可能完全理解或能够模仿这些案例。实现本身可能会改变(例如,在我的 Debian Linux 上,libc 的升级可能会改变 printf 未定义行为

如果你想了解更多printf研究一些C标准库的源代码实现(例如musl-libc,其代码可读性很强)和GCC 实现(假设 Linux 操作系统)。

但是 GNU libc 和 GCC 的维护者(甚至 Linux 内核的维护者,通过系统调用)可以自由地改变 undefined 行为(printf 和其他任何东西)

实际上,如果使用 GCC,请始终使用 gcc -Wall(可能还有 -g)进行编译。不要接受任何警告(因此请改进您自己的代码,直到获得 none)。

您的 printf 格式不正确:标志必须在宽度说明符之前。

在将 * 作为宽度说明符处理后,printf 需要 . 或长度修饰符或转换说明符,0 为 none 其中,行为未定义。

你的库实现 printf 做了一些奇怪的事情,它似乎通过用实际宽度参数替换它来处理 * ......实现的副作用。其他人可能会做其他事情,包括中止程序。如果随后进行 %s 转换,这样的格式错误将特别危险。

将您的代码更改为 printf("Test:%+0*d", 10, 20); 应该会产生预期的输出:

Test:+000000020