为什么 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
我正在尝试发现 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
, andX
conversions, the number of digits to appear after the decimal-point character fora
,A
,e
,E
,f
, andF
conversions, the maximum number of significant digits for theg
andG
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 [....]
因此,故意(或可能取决于)代码中的某些未定义行为是故意的不当行为。不要那样做。在极少数情况下你想这样做(我看不到),请记录下来并在评论中证明你自己。
请注意,如果 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