printf
Z Wikipedii
W językach programowania printf jest rodziną funkcji służących do tworzenia i zapisywania tekstu na podstawie szablonu i zestawu argumentów, które są w ten szablon wstawiane.
Funkcje grupy printf zostały stworzone na potrzeby języka C. Obecnie są dostępne również dla wielu innych języków programowania.
Spis treści |
[edytuj] Rodzina funkcji printf w C
W C występuje wiele funkcji grupy printf. Różnią się one jedynie sposobem przekazywania argumentów, znaczeniem kodu zwrotnego (który w zwykłym printfie zwykle jest pomijany) i tym, co robi się z wygenerowanym tekstem.
Funkcje, które pojawiły się we wczesnych Uniksach:
- int printf (const char *format, ...); - wygenerowany tekst pisany jest na standardowe wyjście.
- int fprintf (FILE *stream, const char *format, ...); - wygenerowany tekst pisany jest do strumienia stream
- int sprintf(char *str, const char *format, ...); - wygenerowany tekst jest zapisywany do bufora str. Funkcji tej nie należy stosować ze względu na niebezpieczeństwo wystąpienia przepełnienia bufora.
W BSD 2.11 pojawiły się wersje dla argumentów przekazywanych przez stdarg:
- int vprintf(const char *format, va_list ap);
- int vfprintf(FILE *stream, const char *format, va_list ap);
- int vsprintf(char *str, const char *format, va_list ap);
W BSD 4.4 pojawiła się funkcja :
- int snprintf(char *str, size_t size, const char *format, ...); - to samo co sprintf, tylko że zapisuje co najwyżej size znaków (wliczając w to końcowe \0), dzięki czemu jest o wiele bezpieczniejsza.
- oraz jej dpowiednik dla stdarg: int vsnprintf(char *str, size_t size, const char *format, va_list ap);
W FreeBSD i GNU libc są obecne też (niestandardowe)
- int asprintf(char **strp, const char *fmt, ...);
- int vasprintf(char **strp, const char *fmt, va_list ap);
które same alokują pamięć pod tekst. Jest to najbezpieczniejsza funkcja printf pisząca do pamięci, jednak nie jest standardowa i może być trochę wolniejsza.
W GNU libc dodatkowo są też obecne:
- int dprintf(int fd, const char *format, ...);
- int vdprintf(int fd, const char *format, va_list ap);
piszące do deskryptora zamiast do strumienia.
Istnieją też odpowiedniki dla szerokich znaków:
- int wprintf(const wchar_t *format, ...);
- int fwprintf(FILE *stream, const wchar_t *format, ...);
- int swprintf(wchar_t *wcs, size_t maxlen, const wchar_t *format, ...);
- int vwprintf(const wchar_t *format, va_list args);
- int vfwprintf(FILE *stream, const wchar_t *format, va_list args);
- int vswprintf(wchar_t *wcs, size_t maxlen, const wchar_t *format, va_list args);
[edytuj] Printf w innych językach
W wielu innych językach występują funkcje printf i sprintf (która wbrew nazwie prawie wszędzie sama alokuje pamięć).
Przykłady:
- C - dla porównania
- printf ("%d+%d=%d\n",2,2,2+2);
- Perl
- printf ("%d+%d=%d\n",2,2,2+2)
- $s=sprintf ("%d+%d=%d\n",2,2,2+2); print $s
- Powłoka
- printf "%d+%d=%d\n" 2 2 $[2+2]
- Ruby
- printf ("%d+%d=%d\n",2,2,2+2)
- s=sprintf ("%d+%d=%d\n",2,2,2+2); print s
- Python posiada operator %, z lewej strony którego jest format a z prawej lista argumentów
- print "%d+%d=%d\n"%(2,2,2+2)
- s = "%d+%d=%d\n"%(2,2,2+2); print s
- Pike posiada funkcje write o funkcjonalności analogicznej do printf. Funkcja alokująca zmienną tekstową jest jednak nazwana sprintf:
- write ("%d+%d=%d\n", 2,2,2+2);
- s = sprintf ("%d+%d=%d\n", 2,2,2+2); write (s);
- Ocaml
- open Printf;; printf "%d+%d=%d\n" 2 2 (2+2);;
- open Printf;; let a = sprintf "%d+%d=%d\n" 2 2 (2+2);; printf "%s" a;;
- Object Pascal (Delphi) posiada funkcję Format, która działa podobnie do sprintf.
- writeln(format('%d+%d=%d'+#13,[2,2,2+2]));
[edytuj] Printf w Uniksie
Systemy uniksowe mają zaimplementowaną funkcję printf jako narzędzie konsolowe.
[edytuj] Przykład
$ printf "%d+%d=%d\n" "2" "2" "2+2"
[edytuj] Składnia szablonów
Szablon to zwykły tekst zawierający pola do uzupełniania. Pole zaczyna się od znaku %, potem mogą wystąpić modyfikatory, a na koniec pojedynczy znak typu pola. Znak % pisze się %%.
Należy zauważyć że znaki specjalne postaci \X nie mają nic wspólnego z printfem i są obsługiwane przez kompilator C w czasie kompilacji lub interpreter w przypadku niektórych innych języków.
Zestaw dozwolonych pól jest różny zależnie od języka i standardu. Krótki przegląd częściej stosowanych pól:
- %d - liczba całkowita ze znakiem w formacie dziesiętnym
- %i - synonim dla %d
- %x - liczba całkowita bez znaku w formacie szesnastkowym, z użyciem małych liter
- %X - liczba całkowita bez znaku w formacie szesnastkowym, z użyciem wielkich liter
- %o - liczba całkowita bez znaku w formacie oktalnym
- %u - liczba całkowita bez znaku w formacie dziesiętnym
- %e - liczba zmiennoprzecinkowa w zapisie naukowym (1.2345e+3)
- %E - liczba zmiennoprzecinkowa w zapisie naukowym (1.2345E+3)
- %f - liczba zmiennoprzecinkowa typu double (float jest automatycznie konwertowany) w zapisie dziesiętnym (123.45)
- %c - liczba całkowita jest konwertowana na bajt o danej wartości
- %s - łańcuch tekstowy
- %p - wskaźnik
- %n - do argumentu zapisywana jest liczba dotychczas zapisanych znaków. Istnienie tego pola prowadzi do dużego niebezpieczeństwa (umożliwia przeprowadzenie niektórych wariantów ataku typu format string) a nie jest zbyt przydatne w praktyce.
Niektóre pola z modyfikatorami:
- %ld - liczba całkowita typu long ze znakiem w formacie dziesiętnym
- %lld - liczba całkowita typu long long ze znakiem w formacie dziesiętnym - w większości popularnych kompilatorów (np. gcc)
- %llu - liczba całkowita typu long long bez znaku w formacie dziesiętnym - j.w.
- %I64d - liczba całkowita typu long long ze znakiem w formacie dziesiętnym - używane w niektórych kompilatorach (np. Dev-cpp)
- %zd - liczba całkowita typu size_t ze znakiem w formacie dziesiętnym
- %hd - liczba całkowita typu short ze znakiem w formacie dziesiętnym
- % d - liczba całkowita ze znakiem w formacie dziesiętnym, w przypadku liczby dodatniej przed liczbą dać spację
- %+d - liczba całkowita typu short ze znakiem w formacie dziesiętnym, zawsze drukować znak
- %04d - liczba całkowita, uzupełniana zerami do czterech miejsc.
- %[^\n]s - w rodzinie sprintf wczytanie całej linii bez znaków kończących linię (CR, LF).
[edytuj] Kwestie bezpieczeństwa
Z użyciem funkcji z rodziny printf wiążą się dwie podstawowe grupy zagrożeń bezpieczeństwa:
- Format string attack - w sytuacji, gdy użytkownik ma możliwość wpływu na użyty łańcuch formatujący, często możliwe jest nadpisanie pamięci i przejęcie kontroli nad procesem z wykorzystaniem funkcjonalności %n. Najczęstszą przyczyną takiej podatności jest skorzystanie przez programistę z konstrukcji printf(zmienna); zamiast printf("%s",zmienna);.
- Przepełnienie bufora - funkcja *sprintf (a także nieprawidłowo użyta *snprintf) może przepełnić bufor przeznaczony przez programistę na zachowanie wyniku tej operacji, tym samym prowadząc do nadpisania struktur sterujących w pamięci procesu i przejęcie nad nim kontroli przez osobę trzecią.
Dodatkowo, pewne scenariusze ataków mogą pojawiać się, gdy dochodzi do niezgodności między budową szablonu a faktycznymi parametrami przekazanymi funkcji *printf - na przykład gdy parametrów jest mniej niż pól w szablonie, albo wartości liczbowe przekazywane są w miejsce wskaźników. Błędy tego typu zwykle prowadzą jednak do nieprawidłowego działania programu i zostają wykryte przez programistę szybciej, niż dwie wymienione wyżej klasy.
[edytuj] Argumenty za i przeciw funkcjom grupy printf oraz funkcjom na nich wzorowanych
Większość nowych języków udostępnia funkcje typu printf, choć zwykle dostępne są też inne mechanizmy, np. w Perlu można wpisywać bezpośrednio w łańcuch tekstowy nazwy zmiennych - np. $x=2;$y=2;$z=2+2; print "$x+$y=$z", a w Ruby nawet całe wyrażenia - print "#{2}+#{2}=#{2+2}\n".
W platformie .NET istnieje podobna funkcja String.Format("{0}+{1}={2}\n", 2, 2, 2 + 2), która dodatkowo umożliwia łatwą zmianę kolejności użycia argumentów.
Niektóre języki, np. Ada, celowo nie posiadają funkcji tego typu. Są one tu praktycznie niemożliwe do implementacji ze względu na zbyt silną kontrolę systemu typów.
Argumenty za:
- są bardzo wygodne
- cały szablon jest w jednym miejscu
- umożliwiają łatwe dodawanie informacji nt. formatu
Przeciw:
- trudno je rozszerzyć o nowe typy
- kompilator nie zawsze może sprawdzić ich poprawności
- mogą wystąpić problemy z bezpieczeństwem
- konieczna znajomość typu każdego wyrażenia