Какая польза от спецификатора формата% n в C?

125

Какая польза от %nспецификатора формата в C? Может ли кто-нибудь объяснить на примере?

мистифицировать
источник
25
Что стало с прекрасным искусством чтения прекрасного руководства?
Jens
8
Я думаю, что реальный вопрос заключается в том, какова ТОЧКА такой опции? зачем кому-то знать значение числа напечатанных char, а тем более записывать это значение непосредственно в память. Как будто разработчикам стало скучно и они решили внести ошибку в ядро
jia chen
Вот почему Bionic отказалась от этого.
solidak 04
1
На самом деле это правильный вопрос, и на него, скорее всего, не ответят хорошие руководства; было обнаружено, что это %nделает printfслучайно завершенным по Тьюрингу, и вы можете, например, реализовать в нем Brainfuck, см. github.com/HexHive/printbf и oilhell.org/blog/2019/02/07.html#appendix-a-minor-sublanguages
Джон Фрейзер

Ответы:

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 03
1
@AndrewS: Потому что функция изменит значение переменной.
Джек
3
@Jack intвсегда подписан.
jamesdlin
1
@jamesdlin: Моя ошибка. Извините .. Я не знала, где это прочитала.
Джек
1
По какой-то причине образец выдает ошибку с пометкой n format specifies disabled. В чем причина?
Johnny_D
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(и, возможно, вносить ошибки), остается предметом споров.)

jamesdlin
источник
3
О боже - это символьная версия вычисления размера строки в пикселях заданного шрифта!
Не могли бы вы объяснить, зачем нужны & n и * 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);
    }
}

где злоумышленник может воспользоваться именем параметра получения передается в Printf в качестве строки формата и использовать комбинацию %d, %cили ж / д идти через стек вызовов , а затем изменить переменную авторизованному к истинному значению.

Да, это эзотерическое использование, но всегда полезно знать при написании демона, чтобы избежать дыр в безопасности? : D

Xzhsh
источник
1
Есть больше причин, чем %nизбегать использования непроверенной входной строки в качестве printfстроки формата.
Кейт Томпсон
13

Из здесь мы видим , что он хранит количество напечатанных символов до сих пор.

n Аргумент должен быть указателем на целое число, в которое записано количество байтов, записанных на данный момент на выходе этим вызовом одной из fprintf()функций. Никакой аргумент не преобразуется.

Пример использования:

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

n_charsтогда будет иметь значение 12.

KLee1
источник
10

Пока все ответы касаются именно %nэтого, но не того, зачем кому-то это вообще нужно. Я считаю, что это несколько полезно с sprintf/ snprintf, когда вам может потребоваться позже разбить или изменить результирующую строку, поскольку сохраненное значение является индексом массива в результирующей строке. Однако это приложение намного полезнее, sscanfтем более что функции в scanfсемействе возвращают не количество обработанных символов, а количество полей.

Еще одно действительно хакерское использование - это бесплатное получение псевдо-журнала10 одновременно с печатью числа как части другой операции.

R .. GitHub НЕ ПОМОГАЕТ ICE
источник
+1 за упоминание использования для %n, хотя я позволю себе отличаться по поводу «всех ответов ...». = P
jamesdlin 04
1
Плохие парни благодарят вас за использование printf /% n, sprintf и sscanf;)
jww
6
@noloader: Как так? Использование %n абсолютно нулевой опасности уязвимости для злоумышленника. Неуместная дурная репутация %nдействительно связана с глупой практикой передачи строки сообщения, а не строки формата в качестве аргумента формата. Эта ситуация, конечно, никогда не возникает, когда %nона фактически используется в преднамеренной строке формата.
R .. GitHub НЕ ПОМОГАЕТ ICE
% n позволяет вам писать в память. Я думаю, вы предполагаете, что злоумышленник не контролирует этот указатель (я могу ошибаться). Если злоумышленник управляет указателем (это просто еще один параметр для printf), он / она может выполнить запись 4 байтов. Другое дело, сможет ли он / она получить прибыль.
jww 09
8
@noloader: Это верно в отношении любого использования указателей. Никто не говорит "спасибо плохим парням" за то, что они написали *p = f();. Почему %nэто просто еще один способ записи результата в объект, на который указывает указатель, следует считать «опасным», а не считать опасным сам указатель?
R .. GitHub НЕ ПОМОГАЕТ ICE
10

Аргумент, связанный с, %nбудет рассматриваться как int*и будет заполнен общим количеством символов, напечатанных в этой точке в printf.

Evan
источник
9

На днях я оказался в ситуации, когда %nбы хорошо решил мою проблему. В отличие от моего более раннего ответа , в этом случае я не могу придумать хорошую альтернативу.

У меня есть элемент управления с графическим интерфейсом, который отображает определенный текст. Этот элемент управления может отображать часть этого текста жирным шрифтом (или курсивом, или подчеркнутым и т. Д.), И я могу указать, какую часть, указав индексы начального и конечного символа.

В моем случае я генерирую текст для 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);
jamesdlin
источник
Я дам вам +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 rich-edit, это не поможет, если вы не захотите потом вернуться и проанализировать последовательности элементов управления терминала. Это также предполагает, что вы хотите соблюдать последовательности управления терминалом в остальной части заменяемого текста; если вы этого не сделаете, вам придется отфильтровать или избежать их. Я не говорю, что без этого невозможно обойтись %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
Судханшу Мишра
источник
когда я пытаюсь написать код, он дает мне: Hello World Characters print so far = 36 ,,,,,, почему 36 ?! Я использую 32-битный GCC на машине с Windows.
Сина Карванди
-6

% n - это C99, не работает с VC ++.

user411313
источник
2
%nсуществовал в C89. Он не работает с MSVC, потому что Microsoft отключила его по умолчанию из соображений безопасности; вы должны _set_printf_count_outputсначала позвонить, чтобы включить его. (См. Ответ Мерлина Моргана-Грэма.)
jamesdlin
Нет, C89 не определяет эту функцию / бэкдорбаг. См. K & R + ANSI-C amazon.com/Programming-Language-2nd-Brian-Kernighan/dp/… ?? где URL-tagger для комментариев ??
user411313 05
4
Вы просто неправы. Это четко указано в Таблице B-1 ( printfпреобразования) Приложения B K&R, 2-е издание. (Страница 244 моей копии.) Или см. Раздел 7.9.6.1 (страница 134) спецификации ISO C90.
jamesdlin 06
Android также удалил спецификатор% n.
jww 09