C中%n格式说明符的用途是什么?


125

%nC中格式说明符的用途是什么?有人可以举例说明吗?


25
阅读精美手册的艺术变成了什么?
詹斯(Jens)

8
我认为真正的问题是这样的期权的要点是什么?为什么有人想知道打印出的char数的值少得多,所以直接将该值写到内存中。就像开发人员感到无聊一样,他们决定将一个错误引入内核
jia chen

这就是为什么仿生放手。
solidak '18

1
实际上,这是一个有效的问题,精美的手册可能不会回答这个问题。已经发现,它%nprintf意外地使图灵完成,并且您可以例如在其中实现Brainfuck,请参阅github.com/HexHive/printbfoilshell.org/blog/2019/02/07.html#appendix-a-minor-sublanguages
约翰·弗雷泽(John Frazer)

Answers:


147

什么都没打印。该参数必须是一个指向有符号int的指针,该整数存储了到目前为止已写入的字符数。

#include <stdio.h>

int main()
{
  int val;

  printf("blah %n blah\n", &val);

  printf("val = %d\n", val);

  return 0;

}

先前的代码显示:

blah  blah
val = 5

1
您提到参数必须是一个指向有符号的int的指针,然后在示例中使用了无符号的int(可能只是一个错字)。
bta 2010年

1
@AndrewS:因为该函数将修改变量的值。
2014年

3
@Jack int始终签名。
jamesdlin

1
@jamesdlin:我的错误。对不起..我不知道在哪里读。
杰克

1
出于某种原因,该示例会产生带有note的错误n format specifies disabled。什么原因?
Johnny_D 2014年

186

这些答案大多数都解释了%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(可能会引入错误)值得商debate。)


3
噢,我的-这是基于字符的版本,可以计算给定字体中字符串的像素大小!

您能否解释为什么需要&n和* s。他们都是指针吗?
Andrew S

9
@AndrewS &n是一个指针(&是地址运算符);指针是必需的,因为C是传递值,并且没有指针printf就无法修改的值n。的%*s在使用printf格式字符串打印一个%s说明符(在此情况下,空字符串""使用)字段宽度n字符。基本printf原理的解释基本上不在此问题(和答案)的范围内;我建议阅读printf文档或在SO上问您自己的单独问题。
jamesdlin

3
感谢您展示用例。我不明白为什么人们基本上只是将手册复制粘贴到SO中,然后有时将其改写。我们是人类,每件事都是出于某种原因,应该始终在答案中加以解释。“什么都不做”就像说“酷意味着酷”这个词-几乎是无用的知识。
the_endian

1
@PSkocik令人费解并且容易出错,而无需添加额外的间接级别。
jamesdlin

18

我还没有真正看到过该%n说明符在现实世界中的许多实际用途,但是我记得它曾在老式的printf漏洞中使用过格式字符串攻击很久。

这样的事情

void authorizeUser( char * username, char * password){

    ...code here setting authorized to false...
    printf(username);

    if ( authorized ) {
         giveControl(username);
    }
}

恶意用户可以利用username参数作为格式字符串传递给printf并结合使用%d%c或W / E去通过调用栈,然后修改授权的真实值的变量。

是的,这是一种深奥的用法,但是在编写守护程序以避免安全漏洞时总是有用的吗?:D


1
除了%n避免使用未经检查的输入字符串作为printf格式字符串外,还有更多原因。
基思·汤普森

13

这里我们看到它存储了到目前为止打印的字符数。

n 参数应该是一个指向整数的指针,到目前为止,通过对该fprintf()函数之一的调用,已将写入输出的字节数写入该整数。没有参数被转换。

一个示例用法是:

int n_chars = 0;
printf("Hello, World%n", &n_chars);

n_chars则其值为12


10

到目前为止,所有答案都是关于这样%n做的,但不是为什么有人首先想要它的原因。我发现使用sprintf/ snprintf时它很有用,当您以后可能需要拆分或修改结果字符串时,因为存储的值是结果字符串中的数组索引。但是,使用sscanf,此应用程序会有用得多,特别是因为scanf系列中的不返回已处理的字符数,而是返回字段数。

另一个真正骇人听闻的用途是,在打印数字作为另一项操作的一部分的同时,免费获得了伪log10。


+1用于提及的用法%n,尽管我希望对“所有答案...”有所不同。= P
jamesdlin

1
坏人感谢您对printf /%n,sprintf和sscanf的使用;)
jww 2013年

6
@noloader:怎么样?使用 %n绝对不会对攻击者造成零风险。放错地方的名声%n确实属于通过传递消息字符串而不是格式字符串作为format参数的愚蠢做法。当这种情况%n实际上是故意使用的故意格式字符串的一部分时,这种情况当然不会发生。
R .. GitHub停止帮助ICE,

%n允许您写入内存。我认为您假设攻击者无法控制该指针(我可能错了)。如果攻击者控制了指针(这只是printf的另一个参数),则他/她可以执行4字节的写操作。他/她是否可以获利是另外一个故事。
2013年

8
@noloader:关于指针的任何使用都是如此。没有人说“坏人谢谢你”*p = f();。为什么%n只是将结果写到指针所指向的对象的另一种方式,而不是认为指针本身是危险的,这才是“危险的”呢?
R .. GitHub停止帮助ICE

10

与关联的参数%n将被视为和,int*并用中在该点打印的总字符数填充printf


9

前几天,我发现自己处在%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);

我会给您+1的用例。但是您将无法通过审核,因此您可能应该设计出另一种方式来标记粗体文本的开头和结尾。似乎snprintf在检查返回值时三个可以正常工作,因为snprintf返回的是写入的字符数。也许是这样的:int begin = snprintf(..., "blah blah %s %f yada yada", ...);int end = snprintf(..., "%s", ...);,然后尾巴:snprintf(..., "blah blah");
jww

3
@jww多个snprintf调用的问题是替换可能会在其他语言环境中重新排列,因此不能像这样拆分它们。
jamesdlin

谢谢你的例子。但是,您难道不喜欢编写一个终端控制序列以使输出在该字段之前加粗,然后在该字段之后编写一个序列吗?如果不对终端控制序列进行硬编码,则也可以使它们处于位置(可重新排序)。
PSkocik

1
@PSkocik 如果要输出到终端。例如,如果您正在使用Win32富编辑控件,那么除非您要返回并随后解析终端控件序列,否则将无济于事。这还假定您要在其余替换文本中使用终端控制序列;如果不这样做,则必须过滤或转义那些内容。我并不是说没有它是不可能的%n;我声称使用%n比替代方法更直接。
jamesdlin

1

它不会打印任何内容。它用于确定%n在格式字符串中出现之前要打印多少个字符,并将其输出到提供的int:

#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;
}

(的文件_set_printf_count_output


0

它将存储到目前为止已打印的字符数的值 printf()功能中。

例:

int a;
printf("Hello World %n \n", &a);
printf("Characters printed so far = %d",a);

该程序的输出将是

Hello World
Characters printed so far = 12

当我尝试使用您的代码时,它会给我:到目前为止,您好打印的世界字符= 36 、、、、为什么36?我在Windows机器中使用32位GCC。
新浪卡凡迪

-6

%n是C99,不适用于VC ++。


2
%n存在于C89中。它不适用于MSVC,因为出于安全考虑,Microsoft默认情况下将其禁用。您必须先致电_set_printf_count_output以启用它。(请参阅Merlyn Morgan-Graham的答案。)
jamesdlin

不,C89不定义此功能/后门错误。请参阅K&R + ANSI-Camazon.com/Programming-Language-2nd-Brian-Kernighan/dp/……URL-tagger所在的位置?
user411313 2010年

4
你错了 它在printfK&R第二版附录B的表B-1(转换)中明确列出。(我的副本的第244页。)或参见ISO C90规范的第7.9.6.1节(第134页)。
jamesdlin

Android还删除了%n说明符。
jww

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.