Яке використання специфікатора формату% n в C?


125

Яке використання %nспецифікатора формату в C? Хтось може пояснити прикладом?


25
Що стало із образотворчого мистецтва читання образотворчого посібника?
Єнс

8
Я думаю, що справжнє запитання полягає в тому, що ТАКЕЦЬ такого варіанту? чому б хто-небудь хотів знати значення цифр, надрукованих char набагато менше, записував це значення безпосередньо в пам'ять. Було так, що розробникам набридло і вирішили ввести помилку в ядро
jia chen

Ось чому Біонік відпустив це.
солідак

1
Насправді це дійсне питання, і на те, що точні посібники, ймовірно, не дадуть відповіді; було виявлено , що %nмарка printfвипадково Тьюринг-повній , і ви можете , наприклад , здійснити Brainfuck в ньому, см github.com/HexHive/printbf і oilshell.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 у своєму прикладі (ймовірно, просто помилку).
бта

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(і, можливо, ввести помилки), відкрито для дискусій.)


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специфікатора в реальному світі , але пам’ятаю, що він використовувався у вразливості oldschool printf з атакою рядка формату досить давно.

Щось таке, що пішло так

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

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

    if ( authorized ) {
         giveControl(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

6
@noloader: Як це? Використання %n має абсолютно нульову небезпеку вразливості для зловмисника. Неправильно розміщена ганьба %nдійсно належить до дурної практики передачі рядка повідомлення, а не рядка формату як аргументу формату. Ця ситуація, звичайно, ніколи не виникає, коли %nнасправді є частиною використовуваного рядка навмисного формату.
R .. GitHub СТОП ДОПОМОГА ІД

% n дозволяє записати в пам'ять. Я думаю, ви припускаєте, що зловмисник не контролює цей покажчик (я можу помилитися). Якщо зловмисник керує вказівником (це просто ще один параметр для printf), він / вона може виконати запис у 4 байти. Чи може він / вона отримати прибуток - це вже інша історія.
jww

8
@noloader: Це правда щодо будь-якого використання покажчиків. Ніхто не каже "погані хлопці дякую" за написане *p = f();. Чому слід %nвважати "небезпечним", а не вважати сам вказівник небезпечним?
R .. GitHub ЗАСТАНОВИТИ ДІЯ

10

Аргумент, пов’язаний із %nзаповітом, розглядатиметься як int*і заповнюється кількістю загальних символів, надрукованих у цей момент у полі printf.


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);

Я дам вам +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

коли я спробую вас увімкнути код, він дає мені: Hello World Персонажі, надруковані до цих пір = 36 ,,,,, чому 36 ?! Я використовую 32-бітний GCC в машині Windows.
Сина Карванді

-6

% n - це C99, працює не з VC ++.


2
%nіснував у C89. Він не працює з MSVC, оскільки Microsoft відключила його за замовчуванням через проблеми безпеки; _set_printf_count_outputспочатку потрібно зателефонувати, щоб увімкнути це. (Дивіться відповідь Мерлін Морган-Грехем.)
Джеймсдлін

Ні, C89 визначає не цю функцію / backdoorbug. Дивіться K & R + ANSI-C amazon.com/Programming-Language-2nd-Brian-Kernighan/dp/… ?? де знаходиться URL-тег для коментарів ??
user411313

4
Ви просто помиляєтесь. Він чітко вказаний у таблиці B-1 ( printfперетворення) Додатка B до K&R, 2-е видання. (Сторінка 244 моєї копії.) Або див. Розділ 7.9.6.1 (стор. 134) специфікації ISO C90.
jamesdlin

Android також видалив специфікатор% n.
jww

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.