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
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 %nrobi (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:FooBar
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.)
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
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);
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.
+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.
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:
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);return0;}
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.
%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ź
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.
%n
żeprintf
przypadkowo 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-sublanguagesOdpowiedzi:
Nic nie jest drukowane. Argument musi być wskaźnikiem do podpisanego int, w którym przechowywana jest liczba dotychczas zapisanych znaków.
Poprzedni kod drukuje:
źródło
int
jest zawsze podpisany.n format specifies disabled
. Jaki jest powód?Większość z tych odpowiedzi wyjaśnia, co
%n
robi (czyli nic nie drukuje i zapisuje liczbę znaków wydrukowanych do tej pory wint
zmiennej), ale jak dotąd nikt tak naprawdę nie podał przykładu jej zastosowania . Tutaj jest jeden:wydrukuje:
z ustawionymi Foo i Bar. (To trywialne zrobić to bez użycia
%n
w tym konkretnym przykładzie i ogólnie zawsze można przerwać to pierwszeprintf
połączenie:To, czy nieco dodatkowa wygoda jest warta użycia czegoś ezoterycznego
%n
(i prawdopodobnie wprowadzenia błędów), jest przedmiotem dyskusji.)źródło
&n
jest wskaźnikiem (&
jest operatorem adresu); wskaźnik jest niezbędny, ponieważ C jest wartością przekazaną i bez wskaźnikaprintf
nie może zmodyfikować wartościn
.%*s
Wykorzystanie wprintf
ciągu formatu drukuje%s
specyfikator (w tym przypadku ciąg pusty""
) przy użyciu szerokość pola zn
postaciami. Wyjaśnienie podstawowychprintf
zasad jest zasadniczo poza zakresem tego pytania (i odpowiedzi); Zalecam przeczytanieprintf
dokumentacji lub zadanie własnego pytania na temat SO.Tak naprawdę nie widziałem wielu praktycznych zastosowań
%n
specyfikatora 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
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
,%c
czy 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
źródło
%n
unikanie używania niezaznaczonego ciągu wejściowego jako ciąguprintf
formatu.Od tutaj widzimy, że przechowuje liczbę znaków drukowanych dotąd.
Przykładowe użycie to:
n_chars
miałby wtedy wartość12
.źródło
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 przypadkusprintf
/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 przydatnasscanf
, 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ódło
%n
, chociaż błagam o rozbieżność co do „wszystkich odpowiedzi ...”. = P%n
nie ma absolutnie żadnego zagrożenia dla atakującego. Niewłaściwa hańba%n
naprawdę 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%n
jest faktycznie częścią zamierzonego ciągu formatu.*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?Argument powiązany z
%n
będzie traktowany jako anint*
i jest wypełniony liczbą wszystkich znaków wydrukowanych w tym miejscu wprintf
.źródło
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ą
snprintf
i 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:
źródło
snprintf
podczas sprawdzania zwracanych wartości będą działać dobrze, ponieważsnprintf
zwraca liczbę zapisanych znaków. Może coś takiego:int begin = snprintf(..., "blah blah %s %f yada yada", ...);
iint end = snprintf(..., "%s", ...);
, a następnie ogona:snprintf(..., "blah blah");
.snprintf
połączeniami polega na tym, że podstawienia mogą być przestawione w innych lokalizacjach, więc nie można ich tak rozdzielić.%n
; Twierdzę, że używanie%n
jest prostsze niż alternatywy.To nic nie drukuje. Służy do
%n
ustalenia , ile znaków zostało wydrukowanych, zanim pojawiło się w ciągu formatu, i wyprowadzi to do podanego int:( Dokumentacja dla
_set_printf_count_output
)źródło
Będzie przechowywać wartość liczby znaków wydrukowanych do tej pory w tej
printf()
funkcji.Przykład:
Wynik tego programu będzie
źródło
% n to C99, nie działa z VC ++.
źródło
%n
istniał 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_output
aby go włączyć. (Zobacz odpowiedźprintf
konwersje) w dodatku B K&R, wydanie 2. (Strona 244 mojej kopii.) Lub patrz sekcja 7.9.6.1 (strona 134) specyfikacji ISO C90._set_printf_count_output