%n
格式说明符在 C 语言中有什么用途?能否举个例子解释一下?
大多数答案都解释了%n
的功能(即不打印任何内容,将到目前为止打印的字符数写入一个int
变量中),但是迄今为止没有人真正给出它的用途的示例。这里有一个:
int n;
printf("%s: %nFoo\n", "hello", &n);
printf("%*sBar\n", n, "");
将会打印:
hello: Foo
Bar
使用Foo和Bar进行对齐。(在这个特定的例子中,没有使用%n
也可以轻松实现此功能,而且通常可以拆分第一个printf
调用:
int n = printf("%s: ", "hello");
printf("Foo\n");
printf("%*sBar\n", n, "");
使用像%n
这样奇怪的东西是否值得为了稍微方便一点点(可能会引入错误)而采用,这还有待商榷。
&n
是一个指针(&
是取地址运算符);由于C语言是传值调用,没有指针,printf
不能修改变量n
的值。在printf
格式字符串中使用%*s
,可以使用长度为n
个字符的字段宽度打印出一个%s
说明符(在这种情况下是空字符串""
)。解释基本的printf
原理不属于这个问题(和答案)的范围;我建议阅读printf
文档或在SO上提出自己的单独问题。 - jamesdlin没有任何输出。该参数必须是指向有符号整数的指针,存储到目前为止写入的字符数。
#include <stdio.h>
int main()
{
int val;
printf("blah %n blah\n", &val);
printf("val = %d\n", val);
return 0;
}
前面的代码打印出:
blah blah
val = 5
int
始终是带符号的。 - jamesdlin我没有真正看到过太多实际的现实世界使用%n
指示符的例子,但我记得它在相当长一段时间以前与格式化字符串攻击一起在老式printf漏洞中使用。
像这样的东西
void authorizeUser( char * username, char * password){
...code here setting authorized to false...
printf(username);
if ( authorized ) {
giveControl(username);
}
}
一个恶意用户可以利用传递给printf的用户名参数作为格式字符串,并使用组合的%d
、%c
或其他方式来遍历调用堆栈,然后将变量authorized修改为true值。
是的,这是一种晦涩的用法,但在编写守护进程以避免安全漏洞时,了解这一点总是很有用的? :D
printf
格式字符串,除了 %n
之外还有更多的原因。 - Keith Thompson前几天我遇到了这样一种情况:使用%n
可以很好地解决我的问题。与我之前的答案不同,这次我想不出更好的替代方案。
我有一个GUI控件,用于显示某些指定的文本。该控件可以将文本的一部分以粗体(或斜体、下划线等)方式显示,并且我可以通过指定起始和结束字符索引来指定哪一部分。
在我的情况下,我正在使用snprintf
向控件生成文本,并且我希望进行一次替换以使其中一个变量加粗。找到要替换的开始和结束索引是很困难的,因为:
字符串包含多个替换值,其中一个替换值是任意用户指定的文本。这意味着对我关心的替换值进行文本搜索可能是模糊的。
格式字符串可能本地化,并且可能使用$
POSIX扩展的位置格式说明符。因此,对于格式说明符本身,在原始格式字符串中进行搜索是不容易的。
本地化方面还意味着我不能轻松地将格式字符串分成多个snprintf
调用。
因此,找到特定替换值周围的索引最直接的方法是进行以下操作:
char buf[256];
int start;
int end;
snprintf(buf, sizeof buf,
"blah blah %s %f yada yada %n%s%n yakety yak",
someUserSpecifiedString,
someFloat,
&start, boldString, &end);
control->set_text(buf);
control->set_bold(start, end);
snprintf
检查返回值就可以了,因为 snprintf
返回已写入字符的数量。也许可以像这样:int begin = snprintf(..., "blah blah %s %f yada yada", ...);
然后 int end = snprintf(..., "%s", ...);
然后是尾部:snprintf(..., "blah blah");
。 - jwwsnprintf
调用的问题在于,在其他语言环境中可能会重新排列替换,因此不能这样拆分。 - jamesdlin%n
的作用,但没有解释为什么有人首先想要它。我发现在使用sprintf
/snprintf
时,它还是有些用处的,因为存储的值是结果字符串中的数组索引,之后你可能需要拆分或修改结果字符串。然而,这个应用程序在sscanf
中更加有用,特别是因为scanf
系列函数不返回处理的字符数,而是返回字段数。
另一个真正hackish的用途是在打印数字作为另一个操作的一部分时,同时获得免费的伪log10。
%n
绝对不会有任何被攻击者利用的危险。%n
被误解为不安全实际上是因为愚蠢的做法——在格式参数中传递消息字符串而不是格式字符串。当%n
真正作为有意的格式字符串的一部分时,这种情况当然永远不会出现。 - R.. GitHub STOP HELPING ICE*p = f();
不会有人说“坏人感谢你”。为什么应该认为将结果写入指针所指向的对象的另一种方式%n
是“危险”的,而不是认为指针本身是危险的? - R.. GitHub STOP HELPING ICE从这里我们可以看到,它存储了迄今为止已打印的字符数。
n
参数应该为一个指向整数的指针,该整数将被写入到目前为止通过此调用所调用的fprintf()
函数输出的字节数。不进行参数转换。
例如用法:
int n_chars = 0;
printf("Hello, World%n", &n_chars);
n_chars
的值将会是 12
。
%n
关联的参数将被视为int*
并填充为在printf
中该点打印的字符总数。它不会打印任何东西。它用于计算在格式字符串中出现%n
之前打印了多少个字符,并将其输出到提供的整数中:
#include <stdio.h>
int main(int argc, char* argv[])
{
int resultOfNSpecifier = 0;
_set_printf_count_output(1); /* Required in visual studio */
printf("Some format string%n\n", &resultOfNSpecifier);
printf("Count of chars before the %%n: %d\n", resultOfNSpecifier);
return 0;
}
(说明:此处为原文,已翻译成中文。)printf()
函数中迄今为止打印的字符数值。
例如:
int a;
printf("Hello World %n \n", &a);
printf("Characters printed so far = %d",a);
Hello World
Characters printed so far = 12
%n
使得printf
意外地成为图灵完备的,你可以在其中实现 Brainfuck 等语言,参见 https://github.com/HexHive/printbf 和 http://www.oilshell.org/blog/2019/02/07.html#appendix-a-minor-sublanguages。 - John Frazer