Jakie jest zastosowanie specyfikatora formatu% n w C?

125

Jakie jest zastosowanie specyfikatora %nformatu w C? Czy ktoś mógłby wyjaśnić na przykładzie?

Josh
źródło
25
Co się stało z piękną sztuką czytania podręcznika?
Jens,
8
Myślę, że prawdziwe pytanie brzmi: jaki jest PUNKT takiej opcji? dlaczego ktoś miałby chcieć znać wartość liczb wypisanych znaków, a tym bardziej zapisywać tę wartość bezpośrednio do pamięci. To było tak, jakby programiści byli znudzeni i postanowili wprowadzić błąd do Kernala
jia chen
Dlatego Bionic to odpuścił.
solidak
1
W rzeczywistości jest to ważne pytanie, na które dobre podręczniki prawdopodobnie nie udzielą odpowiedzi; odkryto, %nże printfprzypadkowo Turing jest kompletny i możesz np. zaimplementować w nim Brainfuck, patrz github.com/HexHive/printbf i oilhell.org/blog/2019/02/07.html#appendix-a-minor-sublanguages
John Frazer

Odpowiedzi:

147

Nic nie jest drukowane. Argument musi być wskaźnikiem do podpisanego int, w którym przechowywana jest liczba dotychczas zapisanych znaków.

#include <stdio.h>

int main()
{
  int val;

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

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

  return 0;

}

Poprzedni kod drukuje:

blah  blah
val = 5
Klawisz gwiazdki
źródło
1
Wspomniałeś, że argument musi być wskaźnikiem do podpisanego int, a następnie użyłeś w swoim przykładzie int bez znaku (prawdopodobnie po prostu literówka).
bta
1
@AndrewS: Ponieważ funkcja zmodyfikuje wartość zmiennej.
Jack
3
@Jack intjest zawsze podpisany.
jamesdlin
1
@jamesdlin: Mój błąd. Przepraszam ... Nie wiedziałem, gdzie to przeczytałem.
Jack
1
Z jakiegoś powodu próbka zawiera błąd z uwagą n format specifies disabled. Jaki jest powód?
Johnny_D,
186

Większość z tych odpowiedzi wyjaśnia, co %n robi (czyli nic nie drukuje i zapisuje liczbę znaków wydrukowanych do tej pory w intzmiennej), ale jak dotąd nikt tak naprawdę nie podał przykładu jej zastosowania . Tutaj jest jeden:

int n;
printf("%s: %nFoo\n", "hello", &n);
printf("%*sBar\n", n, "");

wydrukuje:

hello: Foo
       Bar

z ustawionymi Foo i Bar. (To trywialne zrobić to bez użycia %nw tym konkretnym przykładzie i ogólnie zawsze można przerwać to pierwsze printfpołączenie:

int n = printf("%s: ", "hello");
printf("Foo\n");
printf("%*sBar\n", n, "");

To, czy nieco dodatkowa wygoda jest warta użycia czegoś ezoterycznego %n(i prawdopodobnie wprowadzenia błędów), jest przedmiotem dyskusji.)

jamesdlin
źródło
3
Ojej - to jest znakowa wersja obliczania rozmiaru łańcucha w pikselach w danej czcionce!
Czy mógłbyś wyjaśnić, dlaczego potrzebne są & n i *. Czy obaj są wskazówkami?
Andrew S,
9
@AndrewS &njest wskaźnikiem ( &jest operatorem adresu); wskaźnik jest niezbędny, ponieważ C jest wartością przekazaną i bez wskaźnika printfnie może zmodyfikować wartości n. %*sWykorzystanie w printfciągu formatu drukuje %sspecyfikator (w tym przypadku ciąg pusty "") przy użyciu szerokość pola z npostaciami. Wyjaśnienie podstawowych printfzasad jest zasadniczo poza zakresem tego pytania (i odpowiedzi); Zalecam przeczytanie printfdokumentacji lub zadanie własnego pytania na temat SO.
jamesdlin
3
Dzięki za pokazanie przypadku użycia. Nie rozumiem, dlaczego ludzie po prostu kopiują i wklejają podręcznik do SO i czasami go przeredagowują. Jesteśmy ludźmi i wszystko dzieje się z powodu, który zawsze powinien być wyjaśniony w odpowiedzi. „Nic nie robi” to jak powiedzenie „Słowo fajny znaczy fajny” - prawie bezużyteczna wiedza.
the_endian
1
@PSkocik Jest wystarczająco zagmatwany i podatny na błędy bez dodawania dodatkowego poziomu pośrednictwa.
jamesdlin
18

Tak naprawdę nie widziałem wielu praktycznych zastosowań %nspecyfikatora w prawdziwym świecie , ale pamiętam, że był on używany w oldschoolowych lukach w zabezpieczeniach printf z atakiem typu string string dość dawno temu.

Coś, co poszło w ten sposób

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

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

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

gdzie złośliwy użytkownik może wykorzystać parametr nazwy użytkownika, który zostanie przekazany do printf jako łańcuch formatu i użyć kombinacji %d , %cczy w / e, aby przejść przez stos wywołań, a następnie zmodyfikować zmienną uprawnionego do prawdziwej wartości.

Tak, to ezoteryczne zastosowanie, ale zawsze warto wiedzieć pisząc demona, aby uniknąć luk w zabezpieczeniach? :RE

Xzhsh
źródło
1
Jest więcej powodów niż %nunikanie używania niezaznaczonego ciągu wejściowego jako ciągu printfformatu.
Keith Thompson
13

Od tutaj widzimy, że przechowuje liczbę znaków drukowanych dotąd.

n Argument powinien być wskaźnikiem do liczby całkowitej, do której jest wpisana liczba bajtów zapisanych do tej pory na wyjściu przez wywołanie jednej z fprintf()funkcji. Żaden argument nie jest konwertowany.

Przykładowe użycie to:

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

n_charsmiałby wtedy wartość 12.

KLee1
źródło
10

Jak dotąd wszystkie odpowiedzi dotyczą tego %n, ale nie dlaczego ktokolwiek miałby tego chcieć w pierwszej kolejności. Uważam, że jest to nieco przydatne w przypadku sprintf/ snprintf, gdy może być później konieczne podzielenie lub zmodyfikowanie otrzymanego ciągu, ponieważ przechowywana wartość jest indeksem tablicy w wynikowym ciągu. Ta aplikacja jest jednak znacznie bardziej przydatna sscanf, zwłaszcza że funkcje wscanf rodzinie nie zwracają liczby przetworzonych znaków, ale liczbę pól.

Innym naprawdę hackerskim zastosowaniem jest otrzymanie pseudo-log10 za darmo w tym samym czasie podczas drukowania liczby w ramach innej operacji.

R .. GitHub PRZESTAŃ POMÓC LODOWI
źródło
+1 za wzmiankę o zastosowaniach dla %n, chociaż błagam o rozbieżność co do „wszystkich odpowiedzi ...”. = P
jamesdlin
1
Ci źli dziękują za korzystanie z printf /% n, sprintf i sscanf;)
jww
6
@noloader: Jak to? Użycie programu %n nie ma absolutnie żadnego zagrożenia dla atakującego. Niewłaściwa hańba %nnaprawdę należy do głupiej praktyki przekazywania ciągu komunikatów zamiast ciągu formatu jako argumentu formatu. Taka sytuacja oczywiście nigdy nie pojawia się, gdy %njest faktycznie częścią zamierzonego ciągu formatu.
R .. GitHub PRZESTAŃ POMÓC NA LODZIE
% n pozwala na zapisywanie w pamięci. Myślę, że zakładasz, że napastnik nie kontroluje tego wskaźnika (mogę się mylić). Jeśli atakujący kontroluje wskaźnik (jest to tylko kolejny parametr do printf), może wykonać zapis 4 bajtów. To, czy może zyskać, to inna historia.
jww
8
@noloader: To prawda, jeśli chodzi o użycie wskaźników. Nikt nie mówi „źli, dziękuję” za napisanie *p = f();. Dlaczego %n, co jest po prostu kolejnym sposobem zapisywania wyniku na obiekcie wskazywanym przez wskaźnik, powinno być uważane za „niebezpieczne”, zamiast uważać sam wskaźnik za niebezpieczny?
R .. GitHub PRZESTAŃ POMÓC NA LODZIE
10

Argument powiązany z %nbędzie traktowany jako an int*i jest wypełniony liczbą wszystkich znaków wydrukowanych w tym miejscu w printf.

Evan
źródło
9

Pewnego dnia znalazłem się w sytuacji, w której %nładnie rozwiązałbym mój problem. W przeciwieństwie do mojej wcześniejszej odpowiedzi , w tym przypadku nie mogę wymyślić dobrej alternatywy.

Mam kontrolkę GUI, która wyświetla określony tekst. Ta kontrolka może wyświetlać część tego tekstu pogrubioną czcionką (lub kursywą, podkreśleniem itp.), I mogę określić, która część, określając początkowe i końcowe indeksy znaków.

W moim przypadku generuję tekst do kontrolki za pomocą snprintfi chciałbym, aby jedno z podstawień zostało pogrubione. Znalezienie indeksów początkowych i końcowych do tego podstawienia jest nietrywialne, ponieważ:

  • Ciąg zawiera wiele podstawień, a jednym z nich jest dowolny, określony przez użytkownika tekst. Oznacza to, że wyszukiwanie w tekście podstawienia, na którym mi zależy, jest potencjalnie niejednoznaczne.

  • Łańcuch formatu może być zlokalizowany i może używać $rozszerzenia POSIX dla specyfikatorów formatu pozycyjnego. Dlatego wyszukiwanie oryginalnego ciągu formatu dla samych specyfikatorów formatu jest nietrywialne.

  • Aspekt lokalizacji oznacza również, że nie mogę łatwo podzielić ciągu formatu na wiele wywołań funkcji snprintf.

Dlatego najprostszym sposobem na znalezienie indeksów wokół konkretnego podstawienia byłoby wykonanie:

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
źródło
Dam Ci +1 za przypadek użycia. Ale audyt nie powiedzie się, więc prawdopodobnie powinieneś wymyślić inny sposób zaznaczenia początku i końca pogrubionego tekstu. Wygląda na to, że trzy snprintfpodczas sprawdzania zwracanych wartości będą działać dobrze, ponieważ snprintfzwraca liczbę zapisanych znaków. Może coś takiego: int begin = snprintf(..., "blah blah %s %f yada yada", ...);i int end = snprintf(..., "%s", ...);, a następnie ogona: snprintf(..., "blah blah");.
jww
3
@jww Problem z wieloma snprintfpołączeniami polega na tym, że podstawienia mogą być przestawione w innych lokalizacjach, więc nie można ich tak rozdzielić.
jamesdlin
Dzięki za przykład. Ale czy nie mógłbyś, na przykład, napisać sekwencji sterującej terminalem, aby dane wyjściowe były pogrubione tuż przed polem, a następnie napisać sekwencję po nim? Jeśli nie zakodujesz na stałe sekwencji kontrolnych terminala, możesz ustawić je również jako pozycyjne (ponownie uporządkowane).
PSkocik
1
@PSkocik Jeśli wysyłasz do terminala. Jeśli pracujesz, powiedzmy, z formantem edycji bogatej Win32, to nie pomoże, chyba że chcesz wrócić i przeanalizować sekwencje kontrolne terminala później. To również zakłada, że ​​chcesz przestrzegać sekwencji kontrolnych terminala w pozostałej części zastępowanego tekstu; jeśli tego nie zrobisz, będziesz musiał je odfiltrować lub uciec. Nie mówię, że nie da się obejść bez %n; Twierdzę, że używanie %njest prostsze niż alternatywy.
jamesdlin
1

To nic nie drukuje. Służy do %nustalenia , ile znaków zostało wydrukowanych, zanim pojawiło się w ciągu formatu, i wyprowadzi to do podanego 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;
}

( Dokumentacja dla_set_printf_count_output )

Merlyn Morgan-Graham
źródło
0

Będzie przechowywać wartość liczby znaków wydrukowanych do tej pory w tej printf()funkcji.

Przykład:

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

Wynik tego programu będzie

Hello World
Characters printed so far = 12
Sudhanshu Mishra
źródło
kiedy próbuję kodować, wyświetla mi się: Hello World Znaki wydrukowane do tej pory = 36 ,,,,, dlaczego 36 ?! Używam 32-bitowego GCC na komputerze z systemem Windows.
Sina Karvandi
-6

% n to C99, nie działa z VC ++.

user411313
źródło
2
%nistniał w C89. Nie działa z MSVC, ponieważ Microsoft wyłączył go domyślnie ze względów bezpieczeństwa; musisz najpierw zadzwonić, _set_printf_count_outputaby go włączyć. (Zobacz odpowiedź
Merlyna
Nie, C89 nie definiuje tej funkcji / backdoorbug. Zobacz K & R + ANSI-C amazon.com/Programming-Language-2nd-Brian-Kernighan/dp/... ?? gdzie jest tagger URL dla komentarzy?
user411313
4
Po prostu się mylisz. Jest to wyraźnie wymienione w tabeli B-1 ( printfkonwersje) w dodatku B K&R, wydanie 2. (Strona 244 mojej kopii.) Lub patrz sekcja 7.9.6.1 (strona 134) specyfikacji ISO C90.
jamesdlin
Android usunął również specyfikator% n.
jww