Что стало с прекрасным искусством чтения прекрасного руководства?
Jens
8
Я думаю, что реальный вопрос заключается в том, какова ТОЧКА такой опции? зачем кому-то знать значение числа напечатанных char, а тем более записывать это значение непосредственно в память. Как будто разработчикам стало скучно и они решили внести ошибку в ядро
Вы упоминаете, что аргумент должен быть указателем на подписанный 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:FooBar
с выровненными Foo и Bar. (Это тривиально сделать без использования %nэтого конкретного примера, и в целом всегда можно разбить этот первый printfвызов:
int n = printf("%s: ","hello");
printf("Foo\n");
printf("%*sBar\n", n,"");
Стоит ли для небольшого дополнительного удобства использовать что-то эзотерическое %n(и, возможно, вносить ошибки), остается предметом споров.)
О боже - это символьная версия вычисления размера строки в пикселях заданного шрифта!
Не могли бы вы объяснить, зачем нужны & 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
Есть больше причин, чем %nизбегать использования непроверенной входной строки в качестве printfстроки формата.
Кейт Томпсон
13
Из здесь мы видим , что он хранит количество напечатанных символов до сих пор.
n Аргумент должен быть указателем на целое число, в которое записано количество байтов, записанных на данный момент на выходе этим вызовом одной из fprintf()функций. Никакой аргумент не преобразуется.
Пример использования:
int n_chars =0;
printf("Hello, World%n",&n_chars);
Пока все ответы касаются именно %nэтого, но не того, зачем кому-то это вообще нужно. Я считаю, что это несколько полезно с sprintf/ snprintf, когда вам может потребоваться позже разбить или изменить результирующую строку, поскольку сохраненное значение является индексом массива в результирующей строке. Однако это приложение намного полезнее, sscanfтем более что функции в scanfсемействе возвращают не количество обработанных символов, а количество полей.
Еще одно действительно хакерское использование - это бесплатное получение псевдо-журнала10 одновременно с печатью числа как части другой операции.
+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.
На днях я оказался в ситуации, когда %nбы хорошо решил мою проблему. В отличие от моего более раннего ответа , в этом случае я не могу придумать хорошую альтернативу.
У меня есть элемент управления с графическим интерфейсом, который отображает определенный текст. Этот элемент управления может отображать часть этого текста жирным шрифтом (или курсивом, или подчеркнутым и т. Д.), И я могу указать, какую часть, указав индексы начального и конечного символа.
В моем случае я генерирую текст для snprintfэлемента управления с помощью , и я бы хотел, чтобы одна из замен была выделена жирным шрифтом. Найти начальный и конечный индексы для этой замены нетривиально, потому что:
Строка содержит несколько замен, и одна из замен является произвольным, заданным пользователем текстом. Это означает, что выполнение текстового поиска интересующей меня замены потенциально неоднозначно.
Строка формата может быть локализована и может использовать $расширение POSIX для описателей позиционного формата. Поэтому поиск в исходной строке формата самих спецификаторов формата нетривиален.
Аспект локализации также означает, что я не могу легко разбить строку формата на несколько вызовов snprintf.
Поэтому самый простой способ найти индексы вокруг конкретной замены - это сделать:
Я дам вам +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);return0;}
%nсуществовал в C89. Он не работает с MSVC, потому что Microsoft отключила его по умолчанию из соображений безопасности; вы должны _set_printf_count_outputсначала позвонить, чтобы включить его. (См. Ответ Мерлина Моргана-Грэма.)
Вы просто неправы. Это четко указано в Таблице B-1 ( printfпреобразования) Приложения B K&R, 2-е издание. (Страница 244 моей копии.) Или см. Раздел 7.9.6.1 (страница 134) спецификации ISO C90.
%n
делаетprintf
случайно завершенным по Тьюрингу, и вы можете, например, реализовать в нем Brainfuck, см. github.com/HexHive/printbf и oilhell.org/blog/2019/02/07.html#appendix-a-minor-sublanguagesОтветы:
Ничего не напечатано. Аргумент должен быть указателем на знаковое int, в котором хранится количество записанных символов.
Предыдущий код печатает:
источник
int
всегда подписан.n format specifies disabled
. В чем причина?Большинство из этих ответов объяснить , что
%n
не делает (что не печатать ничего и записать количество напечатанных символов до сих пор кint
переменной), но до сих пор ни один действительно дал пример того , что использовать его имеет. Вот один из них:напечатает:
с выровненными Foo и Bar. (Это тривиально сделать без использования
%n
этого конкретного примера, и в целом всегда можно разбить этот первыйprintf
вызов:Стоит ли для небольшого дополнительного удобства использовать что-то эзотерическое
%n
(и, возможно, вносить ошибки), остается предметом споров.)источник
&n
- указатель (&
оператор адресации); указатель необходим, потому что C передает значение и без указателяprintf
не может изменить значениеn
.%*s
Использование вprintf
строке формата печатает%s
спецификатор (в этом случае пустая строка""
) , используя ширину поля изn
символов. Объяснение основныхprintf
принципов в основном выходит за рамки этого вопроса (и ответа); Я бы рекомендовал прочитатьprintf
документацию или задать свой отдельный вопрос по SO.Я на самом деле не видел много практических применений
%n
спецификатора в реальном мире , но я помню, что он довольно давно использовался в старых уязвимостях printf с атакой строки формата .Что-то вроде этого
где злоумышленник может воспользоваться именем параметра получения передается в Printf в качестве строки формата и использовать комбинацию
%d
,%c
или ж / д идти через стек вызовов , а затем изменить переменную авторизованному к истинному значению.Да, это эзотерическое использование, но всегда полезно знать при написании демона, чтобы избежать дыр в безопасности? : D
источник
%n
избегать использования непроверенной входной строки в качествеprintf
строки формата.Из здесь мы видим , что он хранит количество напечатанных символов до сих пор.
Пример использования:
n_chars
тогда будет иметь значение12
.источник
Пока все ответы касаются именно
%n
этого, но не того, зачем кому-то это вообще нужно. Я считаю, что это несколько полезно сsprintf
/snprintf
, когда вам может потребоваться позже разбить или изменить результирующую строку, поскольку сохраненное значение является индексом массива в результирующей строке. Однако это приложение намного полезнее,sscanf
тем более что функции вscanf
семействе возвращают не количество обработанных символов, а количество полей.Еще одно действительно хакерское использование - это бесплатное получение псевдо-журнала10 одновременно с печатью числа как части другой операции.
источник
%n
, хотя я позволю себе отличаться по поводу «всех ответов ...». = P%n
абсолютно нулевой опасности уязвимости для злоумышленника. Неуместная дурная репутация%n
действительно связана с глупой практикой передачи строки сообщения, а не строки формата в качестве аргумента формата. Эта ситуация, конечно, никогда не возникает, когда%n
она фактически используется в преднамеренной строке формата.*p = f();
. Почему%n
это просто еще один способ записи результата в объект, на который указывает указатель, следует считать «опасным», а не считать опасным сам указатель?Аргумент, связанный с,
%n
будет рассматриваться какint*
и будет заполнен общим количеством символов, напечатанных в этой точке вprintf
.источник
На днях я оказался в ситуации, когда
%n
бы хорошо решил мою проблему. В отличие от моего более раннего ответа , в этом случае я не могу придумать хорошую альтернативу.У меня есть элемент управления с графическим интерфейсом, который отображает определенный текст. Этот элемент управления может отображать часть этого текста жирным шрифтом (или курсивом, или подчеркнутым и т. Д.), И я могу указать, какую часть, указав индексы начального и конечного символа.
В моем случае я генерирую текст для
snprintf
элемента управления с помощью , и я бы хотел, чтобы одна из замен была выделена жирным шрифтом. Найти начальный и конечный индексы для этой замены нетривиально, потому что:Строка содержит несколько замен, и одна из замен является произвольным, заданным пользователем текстом. Это означает, что выполнение текстового поиска интересующей меня замены потенциально неоднозначно.
Строка формата может быть локализована и может использовать
$
расширение POSIX для описателей позиционного формата. Поэтому поиск в исходной строке формата самих спецификаторов формата нетривиален.Аспект локализации также означает, что я не могу легко разбить строку формата на несколько вызовов
snprintf
.Поэтому самый простой способ найти индексы вокруг конкретной замены - это сделать:
источник
snprintf
при проверке возвращаемых значений будут работать нормально, посколькуsnprintf
возвращает количество написанных символов. Может быть , что - то вроде:int begin = snprintf(..., "blah blah %s %f yada yada", ...);
и ,int end = snprintf(..., "%s", ...);
а затем хвост:snprintf(..., "blah blah");
.snprintf
вызовами состоит в том, что замены могут быть переставлены в других регионах, поэтому их нельзя разбить таким образом.%n
; Я утверждаю, что использование%n
проще, чем альтернативы.Он ничего не печатает. Он используется, чтобы выяснить, сколько символов было напечатано до того, как
%n
появилось в строке формата, и вывести это в предоставленный int:( Документация для
_set_printf_count_output
)источник
Он сохранит значение количества символов, напечатанных на данный момент в этой
printf()
функции.Пример:
Результатом этой программы будет
источник
% n - это C99, не работает с VC ++.
источник
%n
существовал в C89. Он не работает с MSVC, потому что Microsoft отключила его по умолчанию из соображений безопасности; вы должны_set_printf_count_output
сначала позвонить, чтобы включить его. (См. Ответ Мерлина Моргана-Грэма.)printf
преобразования) Приложения B K&R, 2-е издание. (Страница 244 моей копии.) Или см. Раздел 7.9.6.1 (страница 134) спецификации ISO C90._set_printf_count_output