Printf

Aktuální verze stránky ještě nebyla zkontrolována zkušenými přispěvateli a může se výrazně lišit od verze recenzované 5. dubna 2015; kontroly vyžadují 72 úprav .

printf (z anglického  print formatted , "formátovaný tisk") - zobecněný název pro rodinu funkcí nebo metod standardních nebo dobře známých komerčních knihoven nebo vestavěných operátorů některých programovacích jazyků používaných pro formátovaný výstup  - výstup do různých proudů hodnot různých typů formátovaných podle dané šablony. Tato šablona je určena řetězcem složeným podle zvláštních pravidel (formátový řetězec).

Nejpozoruhodnějším členem této rodiny je funkce printf , stejně jako řada dalších funkcí odvozených od printfjmen ve standardní knihovně C (která je také součástí standardních knihoven C++ a Objective-C ).

Rodina operačních systémů UNIX má také obslužný program printf , který slouží stejným účelům formátovaného výstupu.

Fortranův operátor FORMAT lze považovat za raný prototyp takové funkce . Řetězcem řízená inferenční funkce se objevila v předchůdcích jazyka C ( BCPL a B ). Ve specifikaci standardní knihovny C získala svou nejznámější podobu (s příznaky, šířkou, přesností a velikostí). Syntaxe řetězce výstupní šablony (někdy nazývaná formátovací řetězec , formátovací řetězec nebo formátovací řetězec ) byla později používána jinými programovacími jazyky (s obměnami, aby vyhovovaly funkcím těchto jazyků). Odpovídající funkce těchto jazyků se zpravidla také nazývají printf a/nebo jeho deriváty.

Některá novější programovací prostředí (jako je .NET ) také používají koncept výstupu řízeného formátovacím řetězcem, ale s odlišnou syntaxí.

Historie

Vzhled

Fortran I již měl operátory, které poskytovaly formátovaný výstup. Syntaxe příkazů WRITE a PRINT zahrnovala štítek odkazující na nespustitelný příkaz FORMAT , který obsahoval specifikaci formátu. Specifikátory byly součástí syntaxe operátoru a kompilátor mohl okamžitě generovat kód, který přímo provádí formátování dat, což zajistilo nejlepší výkon na počítačích té doby. Měly však následující nevýhody:

První prototyp budoucí funkce printf se objevuje v jazyce BCPL v 60. letech 20. století . Funkce WRITEF přebírá formátovací řetězec, který určuje typ dat odděleně od samotných dat v proměnné řetězce (typ byl zadán bez polí příznaků, šířky, přesnosti a velikosti, ale již před ním byl znak procenta %). [1] Hlavním účelem formátovacího řetězce bylo předat typy argumentů (v programovacích jazycích se statickým typováním vyžaduje určení typu předávaného argumentu pro funkci s nepevným seznamem formálních parametrů složitý a neefektivní mechanismus pro předávání typových informací v obecném případě). Samotná funkce WRITEF byla prostředkem pro zjednodušení výstupu: namísto sady funkcí WRCH (výstup znaku), WRITES (výstup řetězce), WRITEN , WRITED , WRITEOCT , WRITEHEX (výstup čísel v různých tvarech), jediné volání byl použit, ve kterém bylo možné prokládat „jen text“ výstupními hodnotami.

Jazyk Bee , který jej následoval v roce 1969 , již používal název printf s jednoduchým formátovacím řetězcem (podobně jako BCPL ), který specifikoval pouze jeden ze tří možných typů a dvě reprezentace čísel: desítkové ( ), osmičkové ( ), řetězce ( ) a znaky ( ) a jediný způsob, jak formátovat výstup v těchto funkcích, bylo přidat znaky před a za výstup hodnoty proměnné. [2]%d%o%s%c

C a deriváty

Od uvedení první verze jazyka C ( 1970 ) se rodina printf stala hlavním výstupním nástrojem formátu. Náklady na analýzu formátovacího řetězce s každým voláním funkce byly považovány za přijatelné a alternativní volání pro každý typ zvlášť nebyla zavedena do knihovny. Specifikace funkce byla zahrnuta v obou existujících jazykových standardech , publikovaných v roce 1990 a 1999 . Specifikace z roku 1999 obsahuje některé inovace ze specifikace z roku 1990.

Jazyk C++ používá standardní knihovnu C (podle standardu z roku 1990), včetně celé rodiny printf .

Jako alternativu poskytuje standardní knihovna C++ sadu vstupních a výstupních tříd proudu. Výstupní příkazy této knihovny jsou typově bezpečné a nevyžadují analýzu formátovacího řetězce pokaždé, když jsou volány. Mnoho programátorů však nadále používá rodinu printf , protože výstupní sekvence s nimi je obvykle kompaktnější a podstata použitého formátu je jasnější.

Objective-C je poměrně „tenký“ doplněk k C a programy na něm mohou přímo využívat funkce rodiny printf .

Použití v jiných programovacích jazycích

Kromě jazyka C a jeho derivátů (C++, Objective-C) používá mnoho dalších programovacích jazyků syntaxi formátovacího řetězce podobnou printf:

Navíc díky obslužnému programu printf , který je součástí většiny systémů podobných UNIXu, se printf používá v mnoha shellových skriptech (pro sh , bash , csh , zsh atd.).

Následovníci

Některé novější jazyky a programovací prostředí také používají koncept výstupu řízeného formátovacím řetězcem, ale s jinou syntaxí.

Například knihovna .Net Core Class Library (FCL) má rodinu metod System.String.Format , System.Console.Write a System.Console.WriteLine , z nichž některá přetížení vydávají svá data podle formátovacího řetězce. Vzhledem k tomu, že úplné informace o typech objektů jsou k dispozici v běhovém prostředí .Net, není nutné tyto informace předávat ve formátovacím řetězci.

Pojmenování rodinných funkcí

Všechny funkce mají ve svém názvu kmen printf . Předpony před názvem funkce znamenají:

Obecné konvence

Všechny funkce berou jako jeden z parametrů formátovací řetězec ( format ) (popis syntaxe řetězce níže). Vrátí počet napsaných (vytištěných) znaků, bez nulového znaku na konci . Počet argumentů obsahujících data pro formátovaný výstup musí být alespoň tolik, kolik je uvedeno ve formátovacím řetězci. Argumenty "Extra" jsou ignorovány.

Funkce rodiny n ( snprintf , vsnprintf ) vracejí počet znaků, které by se vytiskly, kdyby byl parametr n (omezující počet znaků k tisku) dostatečně velký. V případě jednobajtového kódování odpovídá vrácená hodnota požadované délce řetězce (bez znaku null na konci).

Funkce rodiny s ( sprintf , snprintf , vsprintf , vsnprintf ) berou jako svůj první parametr ( s ) ukazatel na oblast paměti, kam bude zapsán výsledný řetězec. Funkce, které nemají limit na počet zapsaných znaků, jsou nebezpečné funkce, protože mohou vést k chybě přetečení vyrovnávací paměti , pokud je výstupní řetězec větší než velikost paměti přidělené pro výstup.

Funkce rodiny f zapisují řetězec do libovolného otevřeného toku ( parametr stream ), zejména do standardních výstupních toků ( stdout , stderr ). fprintf(stdout, format, …)ekvivalentní k printf(format, …).

Funkce rodiny v berou argumenty nikoli jako proměnný počet argumentů (jako všechny ostatní funkce printf), ale jako seznam va list . V tomto případě, když je funkce volána, makro va end se neprovede.

Funkce rodiny w (první znak) jsou omezenou implementací společnosti Microsoft z rodiny funkcí s : wsprintf , wnsprintf , wvsprintf , wvnsprintf . Tyto funkce jsou implementovány v dynamických knihovnách user32.dll a shlwapi.dll ( n funkcí). Nepodporují výstup s plovoucí desetinnou čárkou a wnsprintf a wvnsprintf podporují pouze text zarovnaný doleva.

Funkce rodiny w ( wprintf , swprintf ) implementují podporu pro vícebajtové kódování, všechny funkce této rodiny pracují s ukazateli na vícebajtové řetězce ( wchar_t ).

Funkce rodiny a ( asprintf , vasprintf ) alokují paměť pro výstupní řetězec pomocí funkce malloc , paměť se uvolní při volání procedury, v případě chyby při provádění funkce se paměť nepřiděluje.

Popis funkcí

Názvy parametrů

Popis funkcí

Návratová hodnota: záporná hodnota — znak chyby; v případě úspěchu funkce vrátí počet zapsaných/výstupních bajtů (bez ohledu na nulový bajt na konci), funkce snprintf vypíše počet bajtů, které by byly zapsány, kdyby n bylo dostatečně velké.

Při volání snprintf může být n nula (v tomto případě s může být nulový ukazatel ), v takovém případě se neprovede žádný zápis, funkce pouze vrátí správnou návratovou hodnotu.

Syntaxe formátovacího řetězce

V C a C++ je formátovací řetězec řetězec zakončený nulou. Všechny znaky kromě specifikátorů formátu jsou zkopírovány do výsledného řetězce beze změny. Standardním znakem začátku specifikátoru formátu je znak %( Znak procenta ), k zobrazení samotného znaku %se používá jeho zdvojení %%.

Struktura specifikátoru formátu

Specifikátor formátu vypadá takto:

% [ příznaky ][ šířka ][ . přesnost ][ velikost ] typ

Požadované součásti jsou počáteční znak specifikátoru formátu ( %) a typ .

Vlajky
Podepsat Podepsat jméno Význam Při absenci tohoto znamení Poznámka
- mínus výstupní hodnota je zarovnána doleva v rámci minimální šířky pole napravo
+ plus vždy zadejte znaménko (plus nebo mínus) pro zobrazenou desetinnou číselnou hodnotu pouze pro záporná čísla
  prostor vložte před výsledek mezeru, pokud první znak hodnoty není znaménko Výstup může začínat číslem. Znak + má přednost před znakem mezery. Používá se pouze pro desetinné hodnoty se znaménkem.
# mříž „alternativní forma“ hodnotového výstupu Při výstupu čísel v hexadecimálním nebo osmičkovém formátu bude číslu předcházet znak formátu (0x nebo 0).
0 nula doplňte pole na šířku zadanou v poli šířky escape sekvence se symbolem0 podložka s mezerami Používá se pro typy d , i , o , u , x , X , a , A , e , E , f , F , g , G . Pro typy d , i , o , u , x , X , pokud je zadaná přesnost , je tento příznak ignorován. U ostatních typů je chování nedefinované.

Pokud je zadán příznak mínus '-', je tento příznak také ignorován.

Modifikátor šířky

Šířka (desetinný nebo hvězdička ) určuje minimální šířku pole (včetně znaménka pro čísla). Pokud je reprezentace hodnoty větší než šířka pole, pak je položka mimo pole (například %2i pro hodnotu 100 poskytne hodnotu pole tří znaků), pokud je reprezentace hodnoty menší než zadané číslo, pak bude doplněn (ve výchozím nastavení) mezerami vlevo, chování se může lišit v závislosti na dalších nastavených příznacích. Pokud je jako šířka zadána hvězdička, je šířka pole určena v seznamu argumentů před výstupní hodnotou (například printf( "%0*x", 8, 15 );zobrazí text 0000000f). Pokud je záporný modifikátor šířky zadán tímto způsobem, příznak - se považuje za nastavený a hodnota modifikátoru šířky je nastavena na absolutní.

Modifikátor přesnosti
  • označuje minimální počet znaků, které by se měly objevit při zpracování typů d , i , o , u , x , X ;
  • označuje minimální počet znaků, které se musí objevit za desetinnou čárkou (tečkou) při zpracování typů a , A , e , E , f , F ;
  • maximální počet významných znaků pro typy g a G ;
  • maximální počet znaků, které mají být vytištěny pro typ s ;

Přesnost je specifikována jako tečka následovaná desetinným číslem nebo hvězdičkou ( * ), pokud žádné číslo nebo hvězdička neexistuje (je přítomna pouze tečka), předpokládá se, že číslo je nula. Tečka se používá k označení přesnosti, i když je při výstupu čísel s plovoucí desetinnou čárkou zobrazena čárka.

Pokud je za tečkou uveden znak hvězdičky, pak se při zpracování formátovacího řetězce načte hodnota pole ze seznamu argumentů. (Zároveň, pokud je znak hvězdičky v poli šířky i v poli přesnosti, je nejprve uvedena šířka, potom přesnost a teprve potom hodnota pro výstup). printf( "%0*.*f", 8, 4, 2.5 );Zobrazí například text 002.5000. Pokud je záporný modifikátor přesnosti zadán tímto způsobem, neexistuje žádný modifikátor přesnosti. [19]

Modifikátor velikosti

Pole velikost umožňuje zadat velikost dat předávaných funkci. Potřeba tohoto pole je vysvětlena zvláštnostmi předávání libovolného počtu parametrů funkci v jazyce C: funkce nemůže „nezávisle“ určit typ a velikost přenášených dat, takže informace o typu parametrů a jejich přesná velikost musí být předána explicitně.

Vzhledem k vlivu specifikací velikosti na formátování celočíselných dat je třeba poznamenat, že v jazycích C a C++ existuje řetězec dvojic celočíselných typů se znaménkem a bez znaménka, které jsou v neklesajícím pořadí velikostí uspořádáno takto:

podepsaný typ Nepodepsaný typ
podepsaný char nepodepsaný char
podepsané krátké ( krátké ) unsigned short int ( unsigned short )
podepsané int ( int ) unsigned int ( unsigned )
podepsáno long int ( long ) unsigned long int ( unsigned long )
podepsáno long long int ( long long ) unsigned long long int ( unsigned long long )

Přesné velikosti typů nejsou známy, s výjimkou typů podepsaných a nepodepsaných znaků .

Spárované podepsané a nepodepsané typy mají stejnou velikost a hodnoty reprezentované v obou typech mají stejné zastoupení.

Typ char má stejnou velikost jako podepsané a nepodepsané typy a sdílí sadu reprezentativních hodnot s jedním z těchto typů. Dále se předpokládá, že char  je jiný název pro jeden z těchto typů; takový předpoklad je pro tuto úvahu přijatelný.

C má navíc typ _Bool , zatímco C++ má typ bool .

Při předávání argumentů funkci, které neodpovídají formálním parametrům v prototypu funkce (což jsou všechny argumenty obsahující výstupní hodnoty), procházejí tyto argumenty standardními povýšeními , konkrétně:

  • argumenty float jsou přetypovány na double ;
  • argumenty typů unsigned char , unsigned short , signed char a short jsou přetypovány na jeden z následujících typů:
    • int , pokud je tento typ schopen reprezentovat všechny hodnoty původního typu, popř
    • jinak nepodepsáno ;
  • argumenty typu _Bool nebo bool jsou přetypovány na typ int .

Funkce printf tedy nemohou přebírat argumenty typu float , _Bool nebo bool , ani celočíselné typy menší než int nebo unsigned .

Sada použitých specifikátorů velikosti závisí na specifikátoru typu (viz níže).

specifikátor %d, %i, %o, %u, %x_%X %n Poznámka
chybějící int nebo unsigned int ukazatel na int
l long int nebo unsigned long int ukazatel na dlouhý int
hh Argument je typu int nebo unsigned int , ale je nucen zadat podepsaný char nebo unsigned char , v daném pořadí ukazatel na podepsaný char formálně existují v C od standardu z roku 1999 a v C++ od standardu z roku 2011.
h Argument je typu int nebo unsigned int , ale je nucen zadat short int nebo unsigned short int , resp . ukazatel na krátký int
ll long long int nebo unsigned long long int ukazatel na long long int
j intmax_t nebo uintmax_t ukazatel na intmax_t
z size_t (nebo typ se znaménkem ekvivalentní velikosti) ukazatel na podepsaný typ ekvivalentní velikosti size_t
t ptrdiff_t (nebo ekvivalentní typ bez znaménka) ukazatel na ptrdiff_t
L __int64 nebo nepodepsaný __int64 ukazatel na __int64 Pro Borland Builder 6 (specifikátor lločekává 32bitové číslo)

Specifikace ha hhslouží ke kompenzaci standardních typových propagací ve spojení s přechody z podepsaných na nepodepsané typy nebo naopak.

Zvažte například implementaci C, kde je typ char podepsán a má velikost 8 bitů, typ int má velikost 32 bitů a je použit další způsob kódování záporných celých čísel.

char c = 255 ; printf ( "%X" , c );

Takové volání vytvoří výstup FFFFFFFF, který nemusí být takový, jaký programátor očekával. Hodnota c je skutečně (char)(-1) a po povýšení typu je -1 . Použití formátu %Xzpůsobí, že daná hodnota bude interpretována jako bez znaménka, tedy 0xFFFFFFFF .

char c = 255 ; printf ( "%X" , ( unsigned char ) c ); char c = 255 ; printf ( "%hhX" , c );

Tato dvě volání mají stejný účinek a vytvářejí výstup FF. První možnost umožňuje vyhnout se násobení znamének při propagaci typu, druhá jej kompenzuje již „uvnitř“ funkce printf .

specifikátor %a, %A, %e, %E, %f, %F, %g_%G
chybějící dvojnásobek
L dlouhý dvojitý
specifikátor %c %s
chybějící Argument je typu int nebo unsigned int , ale je nucen zadat char char*
l Argument je typu wint_t , ale je nucen zadat wchar_t wchar_t*
Specifikátor typu

Typ udává nejen typ hodnoty (z pohledu programovacího jazyka C), ale i konkrétní vyjádření výstupní hodnoty (např. čísla mohou být zobrazena v desítkové nebo šestnáctkové podobě). Psáno jako jeden znak. Na rozdíl od jiných oborů je to povinné. Maximální podporovaná velikost výstupu z jedné sekvence escape je standardně alespoň 4095 znaků; v praxi většina kompilátorů podporuje podstatně větší objemy dat.

Hodnoty typu:

  • d , i  — desetinné číslo se znaménkem, výchozí typ je int . Standardně se píše se zarovnáním vpravo, znaménko se píše pouze pro záporná čísla. Na rozdíl od funkcí v rodině scanf jsou pro funkce v rodině printf specifikace %d a %i zcela synonymní;
  • o  — osmičkové číslo bez znaménka, výchozí typ je unsigned int ;
  • u  je dekadické číslo bez znaménka, výchozí typ je unsigned int ;
  • x a X  jsou hexadecimální čísla bez znaménka, x používá malá písmena (abcdef), X používá velká písmena (ABCDEF), výchozí typ je unsigned int ;
  • f a F  jsou čísla s plovoucí desetinnou čárkou, výchozí typ je double . Standardně se vypisují s přesností 6, pokud je číslo modulu menší než jedna, zapíše se před desetinnou čárku 0. Hodnoty ±∞ jsou uvedeny ve tvaru [-]inf nebo [-]nekonečno (v závislosti na platformě); hodnota Nan je reprezentována jako [-]nan nebo [-]nan (jakýkoli text níže) . Použití F vytiskne zadané hodnoty velkými písmeny ( [-]INF , [-]INFINITY , NAN ).
  • e a E  jsou čísla s plovoucí desetinnou čárkou v exponenciálním zápisu (ve tvaru 1.1e+44), výchozí typ je double . e vypíše znak "e" malým písmenem, E  - velkým písmenem (3.14E+0);
  • g a G  je číslo s plovoucí desetinnou čárkou, výchozí typ je double . Forma zobrazení závisí na hodnotě veličiny ( f nebo e ). Formát se mírně liší od plovoucí desetinné čárky v tom, že úvodní nuly vpravo od desetinné čárky nejsou na výstupu. Pokud je číslo celé číslo, není také zobrazen středník;
  • a a A (počínaje standardy jazyka C z roku 1999 a C++ z roku 2011) — číslo s plovoucí desetinnou čárkou v hexadecimálním tvaru, výchozí typ je double ;
  • c  — výstup symbolu s kódem odpovídajícím předávanému argumentu, výchozí typ je int ;
  • s  - výstup řetězce s ukončovacím bajtem null; pokud je modifikátor délky l , je na výstupu řetězec wchar_t* . V systému Windows závisí hodnoty typu s na typu použitých funkcí. Pokud je použita rodina printffunkcí, pak s označuje řetězec char* . Pokud je použita rodina wprintffunkcí, pak s označuje řetězec wchar_t* .
  • S  je stejné jako s s modifikátorem délky l ; Ve Windows závisí hodnota typu S na typu použitých funkcí. Pokud je použita rodina printffunkcí, pak S znamená řetězec wchar_t* . Pokud je použita rodina wprintffunkcí, pak S označuje řetězec char* .
  • p - pointer  output , vzhled se může výrazně lišit v závislosti na interní reprezentaci v kompilátoru a platformě (například 16bitová platforma MS-DOS používá zápis formuláře FFEC:1003, 32bitová platforma s plochým adresováním používá adresu formuláře 00FA0030);
  • n  - záznam ukazatelem, předaný jako argument, počet znaků zapsaných v době výskytu sekvence příkazů obsahujících n ;
  • %  - znak pro zobrazení znaku procenta (%), který se používá k povolení výstupu znaků procent v řetězci printf, vždy používaný ve formuláři %%.
Výstup čísel s pohyblivou řádovou čárkou

V závislosti na aktuálním národním prostředí lze při zobrazování čísel s plovoucí desetinnou čárkou použít čárku i tečku (a případně další symbol). Chování printf vzhledem ke znaku oddělujícímu zlomkovou a celočíselnou část čísla je určeno používaným národním prostředím (přesněji proměnnou LC NUMERIC ). [dvacet]

Speciální makra pro rozšířenou sadu celočíselných aliasů datových typů

Druhý standard C (1999) poskytuje rozšířenou sadu aliasů pro celočíselné datové typy int N _t , uint N _t , int_least N _t , uint_least N _t , int_fast N _t , uint_fast N _t (kde N je požadovaná bitová hloubka), intptr_t , uintptr_t , intmax_t , uintmax_t .

Každý z těchto typů se může, ale nemusí, shodovat s některým ze standardních integrovaných celočíselných typů. Formálně řečeno, při psaní přenosného kódu programátor předem neví, jakou standardní nebo rozšířenou specifikaci velikosti by měl použít.

int64_t x = 100000000000 ; int šířka = 20 ; printf ( "%0*lli" , šířka , x ); Špatně, protože int64_t nemusí být totéž jako long long int .

Aby bylo možné odvodit hodnoty objektů nebo výrazů těchto typů přenosným a pohodlným způsobem, implementace definuje pro každý z těchto typů sadu maker, jejichž hodnotami jsou řetězce kombinující specifikace velikosti a typu.

Názvy maker jsou následující:

Dvojice podepsaných a nepodepsaných typů Název makra
int N_t a uint N_t _ _ PRITN
int_least N_t a uint_least N_t _ _ PRITLEASTN
int_fastN_t a uint_fastN_t _ _ _ _ PRITFASTN
intmax_t a uintmax_t PRITMAX
intptr_t a uintptr_t PRITPTR

Zde T je jedna z následujících typových specifikací: d, i, u, o, x, X.

int64_t x = 100000000000 ; int šířka = 20 ; printf ( "%0*" PRIi64 , šířka , x ); Správný způsob výstupu hodnoty typu int64_t v jazyce C.

Můžete si všimnout, že typy intmax_t a uintmax_t mají standardní specifikátor velikosti j, takže makro je s největší pravděpodobností vždy definováno jako . PRITMAX"jT"

Rozšíření XSI v jednotném unixovém standardu

Podle standardu Single UNIX (prakticky ekvivalentní standardu POSIX ) jsou následující doplňky printf definovány ve vztahu k ISO C pod rozšířením XSI (X/Open System Interface):

  • Přidána možnost výstupu libovolného parametru podle čísla (uvedeno jako n$bezprostředně za znakem začátku řídicí sekvence, např. printf("%1$d:%2$.*3$d:%4$.*3$d\n", hour, min, precision, sec);).
  • Přidán příznak "'" (jednoduché uvozovky), který pro typy d , i , o , u předepisuje samostatné třídy s odpovídajícím znakem.
  • typ C ekvivalentní lc ISO C (znakový výstup typu wint_t ).
  • typ S ekvivalentní ls ISO C (výstup řetězce jako wchar_t* )
  • Přidány chybové kódy EILSEQ, EINVAL, ENOMEM, EOVERFLOW.

Nestandardní rozšíření

Knihovna GNU C

Knihovna GNU C ( libc ) přidává následující rozšíření:

  • typ m vypíše hodnotu globální proměnné errno (kód chyby poslední funkce).
  • typ C je ekvivalentní lc .
  • příznak ' (jednoduché uvozovky) se používá k oddělení tříd při tisku čísel. Formát separace závisí na LC_NUMERIC
  • velikost q označuje typ long long int (na systémech, kde long long int není podporováno , je to stejné jako long int
  • size Z je alias pro z , byl zaveden do knihovny libc před příchodem standardu C99 a nedoporučuje se pro použití v novém kódu.
Registrace vlastních typů

GNU libc podporuje vlastní registraci typů, což umožňuje programátorovi definovat výstupní formát pro jejich vlastní datové struktury. Pro registraci nového typu použijte funkci
int register_printf_function (int type, printf_function handler-function, printf_arginfo_function arginfo-function), kde:

  • type  — písmeno pro typ (pokud typ = 'Y', pak bude hovor vypadat jako '%Y');
  • handler-function  - ukazatel na funkci, která je volána funkcemi printf, pokud je ve formátovacím řetězci nalezen typ zadaný v type ;
  • arginfo-funkce  je ukazatel na funkci, která bude volána funkcí parse_printf_format .

Kromě definování nových typů umožňuje registrace předefinování existujících typů (například s , i ).

Microsoft Visual C

Microsoft Visual Studio pro programovací jazyky C/C++ ve formátu specifikace printf (a další funkce rodiny) poskytuje následující rozšíření:

  • velikost krabice:
hodnota pole Typ
I32 podepsané __int32 , nepodepsané __int32
I64 podepsané __int64 , nepodepsané __int64
ptrdiff_t , velikost_t
w ekvivalentní l pro řetězce a znaky
javor

Matematické prostředí Maple má také funkci printf, která má následující vlastnosti:

Formátování
    • %a, %A: Objekt Maple bude vrácen v textové notaci, to funguje pro všechny objekty (např. matice, funkce, moduly atd.). Malé písmeno instruuje, aby se znaky (jména) obklopily zadními zaškrtávacími znaménky, které by měly být ve vstupu do printf obklopeny zpětnými znaménky.
    • %q, %Q: stejné jako %a/%A, ale nebude zpracován pouze jeden argument, ale vše počínaje tím, který odpovídá příznaku formátování. Příznak %Q/%q se tedy může ve formátovacím řetězci objevit pouze jako poslední.
    • %m: Formátovat objekt podle jeho vnitřní reprezentace Maple. Prakticky se používá k zápisu proměnných do souboru.

Příklad:

> printf("%a =%A", `+`, `+`); `+` = + > printf("%a =%m", `+`, `+`); `+` = I"+f*6"F$6#%(builtinGF$"$Q"F$F$F$F"%*chráněnoG Závěr

Funkce fprintf Maple bere jako svůj první argument buď deskriptor souboru (vrácený fopen), nebo název souboru. V druhém případě musí být název typu „symbol“, pokud název souboru obsahuje tečky, pak musí být uzavřen v backticks nebo převeden pomocí funkce convert (název_souboru, symbol).

Zranitelnosti

Funkce rodiny printf berou seznam argumentů a jejich velikost jako samostatný parametr (ve formátovacím řetězci). Nesoulad mezi formátovacím řetězcem a předávanými argumenty může vést k nepředvídatelnému chování, poškození zásobníku, spuštění libovolného kódu a zničení oblastí dynamické paměti. Mnoho funkcí rodiny se nazývá „unsafe“ ( anglicky  unsafe ), protože nemají ani teoretickou schopnost chránit před nesprávnými údaji.

Také funkce rodiny s (bez n , jako je sprintf , vsprintf ) nemají žádná omezení na maximální velikost zapisovaného řetězce a mohou vést k chybě přetečení vyrovnávací paměti (když jsou data zapisována mimo alokovanou paměťovou oblast).

Chování, když se formátovací řetězec a předané argumenty neshodují

V rámci konvence volání cdecl se čištění zásobníku provádí pomocí funkce volání. Při volání printf se argumenty (nebo ukazatele na ně) umístí v pořadí, v jakém jsou zapsány (zleva doprava). Při zpracovávání formátovacího řetězce čte funkce printf argumenty ze zásobníku. Jsou možné následující situace:

  • počet a typ argumentů se shoduje s těmi, které jsou uvedeny ve formátovacím řetězci (běžná operace funkce)
  • více argumentů předávaných funkci, než je uvedeno ve formátovacím řetězci (argumenty navíc)
  • Méně argumentů předáno funkci, než vyžaduje formátovací řetězec (nedostatečné argumenty)
  • Argumenty nesprávné velikosti předány funkci
  • Funkce byly předány argumenty správné velikosti, ale nesprávného typu

Specifikace jazyka C popisují pouze dvě situace (normální provoz a další argumenty). Všechny ostatní situace jsou chybné a vedou k nedefinovanému chování programu (ve skutečnosti vede k libovolným výsledkům až k provádění neplánovaných částí kódu).

Příliš mnoho argumentů

Při předávání nadměrného počtu argumentů funkce printf přečte argumenty potřebné ke správnému zpracování formátovacího řetězce a vrátí se k volající funkci. Volající funkce v souladu se specifikací vymaže zásobník od parametrů předávaných volané funkci. V tomto případě se dodatečné parametry jednoduše nepoužívají a program pokračuje beze změn.

Nedostatek argumentů

Pokud je v zásobníku při volání printf méně argumentů, než je potřeba ke zpracování formátovacího řetězce, pak se chybějící argumenty načtou ze zásobníku, a to navzdory skutečnosti, že v zásobníku jsou libovolná data (nerelevantní pro práci printf ) . Pokud bylo zpracování dat „úspěšné“ (to znamená, že nedošlo k ukončení programu, zablokování nebo zápisu do zásobníku), po návratu k volající funkci se hodnota ukazatele zásobníku vrátí na původní hodnotu a program pokračuje.

Při zpracování hodnot zásobníku „navíc“ jsou možné následující situace:

  • Úspěšné načtení "extra" parametru pro výstup (číslo, ukazatel, symbol atd.) - "téměř náhodná" hodnota načtená ze zásobníku se umístí do výstupních výsledků. To nepředstavuje nebezpečí pro chod programu, ale může to vést ke kompromitaci některých dat (výstup hodnot zásobníku, které může útočník použít k analýze provozu programu a získání přístupu k interním/soukromým informacím programu).
  • Chyba při čtení hodnoty ze zásobníku (například v důsledku vyčerpání dostupných hodnot zásobníku nebo přístupu na „neexistující“ stránky paměti) – taková chyba s největší pravděpodobností způsobí pád programu.
  • Čtení ukazatele na parametr. Řetězce jsou předávány pomocí ukazatele, při čtení „libovolné“ informace ze zásobníku se načtená (téměř náhodná) hodnota použije jako ukazatel na náhodnou oblast paměti. Chování programu v tomto případě není definováno a závisí na obsahu této paměťové oblasti.
  • Zápis parametru ukazatelem ( %n) - v tomto případě je chování podobné jako u čtení, ale je komplikováno možnými vedlejšími efekty zápisu do libovolné paměťové buňky.
Neshoda typu argumentu

Formálně jakýkoli rozpor mezi typem argumentu a očekáváním způsobí nedefinované chování programu. V praxi existuje několik případů, které jsou zvláště zajímavé z hlediska programátorské praxe:

  • Argument je stejného typu, jak se očekávalo, ale má jinou velikost.
  • Argument má stejnou velikost, jak se očekávalo, ale jiný typ.

Jiné případy zpravidla vedou ke zjevně nesprávnému chování a jsou snadno odhalitelné.

Neshoda velikosti celého čísla nebo argumentu s pohyblivou řádovou čárkou

Pro celočíselný argument (se specifikací celočíselného formátu) jsou možné následující situace:

  • Předávání parametrů, které jsou větší, než se očekávalo (čtení menších od větších). V tomto případě, v závislosti na přijatém pořadí bajtů a směru růstu zásobníku, se může zobrazená hodnota shodovat s hodnotou argumentu nebo se ukázat, že s ní nesouvisí.
  • Předávání parametrů, které jsou menší, než se očekávalo (čtení větší od menšího). V tomto případě je možná situace, kdy se čtou oblasti zásobníku, které přesahují limity předávaných argumentů. Chování funkce je v tomto případě podobné chování v situaci s nedostatkem parametrů. Obecně platí, že výstupní hodnota neodpovídá očekávané hodnotě.

V případě skutečného argumentu (se specifikací skutečného formátu) v případě jakékoli neshody velikosti výstupní hodnota zpravidla neodpovídá předané hodnotě.

Je pravidlem, že pokud je velikost některého argumentu nesprávná, správné zpracování všech následujících argumentů se stává nemožným, protože do ukazatele na argumenty je vložena chyba. Tento efekt však lze kompenzovat zarovnáním hodnot na zásobníku.

Zarovnání hodnot na zásobníku

Mnoho platforem má pravidla zarovnání celých čísel a/nebo skutečných hodnot, která vyžadují (nebo doporučují), aby byly umístěny na adresy, které jsou násobky jejich velikosti. Tato pravidla platí také pro předávání argumentů funkcí na zásobníku. V tomto případě může řada neshod v typech očekávaných a skutečných parametrů zůstat bez povšimnutí, což vytváří iluzi správného programu.

uint32_t a = 1 ; uint64_t b = 2 , c = 3 ; printf ( "%" PRId64 "%" PRId64 "%" PRId64 , b , a , c ); V tomto příkladu má skutečný parametr atypu uint32_tneplatnou specifikaci formátu přidruženou %"PRId64"k typu uint64_t. Na některých platformách s 32bitovým typem intvšak může v závislosti na přijatém pořadí bajtů a směru růstu zásobníku zůstat chyba bez povšimnutí. Skutečné parametry ba cbudou zarovnány na adresu, která je násobkem jejich velikosti (dvojnásobek velikosti a). A „mezi“ hodnotami azůstane bprázdné (obvykle vynulované) místo o velikosti 32 bitů; při zpracování kusovníku bude %"PRId64"32bitová hodnota aspolu s tímto prázdným místem interpretována jako jediná 64bitová hodnota.

Taková chyba se může neočekávaně objevit při portování programového kódu na jinou platformu, změně kompilátoru nebo režimu kompilace.

Potenciální nesoulad velikosti

Definice jazyků C a C++ popisují pouze nejobecnější požadavky na velikost a reprezentaci datových typů. Proto se na mnoha platformách ukazuje, že reprezentace některých formálně odlišných datových typů je stejná. To způsobí, že některé neshody typu zůstanou po dlouhou dobu nezjištěny.

Například na platformě Win32 se obecně uznává, že velikosti typů inta long intjsou stejné (32 bitů). Volání printf("%ld", 1)nebo printf("%d", 1L)bude tedy provedeno „správně“.

Taková chyba se může neočekávaně objevit při portování programového kódu na jinou platformu, změně kompilátoru nebo režimu kompilace.

Při psaní programů v jazyce C++ je třeba dávat pozor na odvozování hodnot proměnných deklarovaných pomocí celočíselných aliasů, zejména size_t, a ptrdiff_t; formální definice standardní knihovny C++ odkazuje na první standard C (1990). Druhý standard C (1999) definuje specifikátory velikosti pro typy size_ta pro řadu dalších typů pro použití s ​​podobnými objekty. ptrdiff_tMnoho implementací C++ je také podporuje.

velikost_ts = 1 ; _ printf ( "%u" , s ); Tento příklad obsahuje chybu, která se může vyskytnout na platformách, sizeof (unsigned int)kde sizeof (size_t). velikost_ts = 1 ; _ printf ( "%zu" , s ); Správný způsob, jak odvodit hodnotu typového objektu, je size_tv jazyce C. Neshoda typu při shodě velikosti

Pokud jsou předané argumenty stejné velikosti, ale mají jiný typ, pak program často poběží "téměř správně" (nezpůsobí chyby přístupu do paměti), ačkoli výstupní hodnota pravděpodobně nebude mít žádný význam. Je třeba poznamenat, že míchání párových celočíselných typů (se znaménkem a bez znaménka) je přípustné, nezpůsobuje nedefinované chování a někdy se v praxi záměrně používá.

Při použití specifikace formátu %sbude hodnota argumentu typu celé číslo, real nebo ukazatel jiného než char*, interpretována jako adresa řetězce. Tato adresa, obecně řečeno, může libovolně ukazovat na neexistující nebo nepřístupnou oblast paměti, což povede k chybě přístupu do paměti, nebo na oblast paměti, která neobsahuje řádek, což povede k nesmyslnému výstupu, možná velmi velkému .

Chyba zabezpečení formátovacího řetězce

Vzhledem k tomu , že printf (a další funkce rodiny) může vytisknout text formátovacího řetězce beze změn, pokud neobsahuje escape sekvence, pak je výstup textu příkazem možný .
printf(text_to_print);
Pokud je text_to_print získán z externích zdrojů (čtení ze souboru , přijaté od uživatele nebo operačního systému), pak přítomnost znaku procenta ve výsledném řetězci může vést k extrémně nežádoucím důsledkům (až k zamrznutí programu).

Příklad nesprávného kódu:
printf(" Current status: 99% stored.");
Tento příklad obsahuje escape sekvenci "% s" obsahující znak escape sekvence (%), příznak (mezera) a datový typ řetězce ( s ). Funkce se po obdržení řídicí sekvence pokusí přečíst ukazatel na řetězec ze zásobníku. Protože funkci nebyly předány žádné další parametry, hodnota, která se má číst ze zásobníku, není definována. Výsledná hodnota bude interpretována jako ukazatel na řetězec ukončený nulou. Výstup takového "řetězce" může vést k libovolnému výpisu paměti, chybě přístupu k paměti a poškození zásobníku. Tento typ zranitelnosti se nazývá útok formátovacím řetězcem .  [21]

Přetečení vyrovnávací paměti

Funkce printf při výstupu výsledku není omezena maximálním počtem výstupních znaků. Pokud se v důsledku chyby nebo přehlédnutí zobrazí více znaků, než se očekávalo, nejhorší věc, která se může stát, je „zničení“ obrazu na obrazovce. Funkce sprintf , vytvořená analogicky s printf , také nebyla omezena maximální velikostí výsledného řetězce. Na rozdíl od "nekonečného" terminálu je však paměť, kterou aplikace pro výsledný řetězec alokuje, vždy omezená. A v případě překročení očekávaných limitů se záznam provádí do paměťových oblastí patřících do jiných datových struktur (nebo obecně do nepřístupných paměťových oblastí, což znamená, že program padá téměř na všech platformách). Zápis do libovolných oblastí paměti vede k nepředvídatelným efektům (které se mohou objevit mnohem později a ne ve formě chyby programu, ale v podobě poškození uživatelských dat). Absence omezení maximální velikosti řetězce je zásadní chybou plánování při vývoji funkce. Z tohoto důvodu mají funkce sprintf a vsprintf nebezpečný stav . Místo toho vyvinul funkce snprintf , vsnprintf , které přebírají další argument omezující maximální výsledný řetězec. Funkce swprintf , která se objevila mnohem později (pro práci s vícebajtovým kódováním), tento nedostatek zohledňuje a bere argument pro omezení výsledného řetězce. (Proto zde není žádná funkce snwprintf ).

Příklad nebezpečného volání sprintf :

charbuffer[65536]; char* name = get_user_name_from_keyboard(); sprintf(buffer, "Uživatelské jméno:%s", jméno);

Výše uvedený kód implicitně předpokládá, že uživatel nebude na klávesnici psát 65 tisíc znaků a vyrovnávací paměť „by měla stačit“. Uživatel však může přesměrovat vstup z jiného programu nebo zadat více než 65 000 znaků. V tomto případě budou oblasti paměti poškozeny a chování programu se stane nepředvídatelným.

Obtíže při používání

Nedostatek kontroly typu

Funkce rodiny printf používají datové typy C. Velikosti těchto typů a jejich poměry se mohou lišit platformu od platformy. Například na 64bitových platformách se mohou velikosti typů int a long lišit v závislosti na zvoleném modelu ( LP64 , LLP64 nebo ILP64 ). Pokud programátor nastaví formátovací řetězec na „téměř správný“, bude kód fungovat na jedné platformě a na jiné poskytne nesprávný výsledek (v některých případech to může vést k poškození dat).

Kód například printf( "text address: 0x%X", "text line" );funguje správně na 32bitové platformě ( velikost ptrdiff_t a velikost int 32 bitů) a na 64bitovém modelu IPL64 (kde velikosti ptrdiff_t a int jsou 64 bitů), ale na 64 bitech poskytne nesprávný výsledek. -bitová platforma modelu LP64 nebo LLP64, kde velikost ptrdiff_t je 64 bitů a velikost int je 32 bitů. [22]

V Oracle Javaprintf se používají zabalené typy s dynamickou identifikací v analogu funkce , [6] v Embarcadero Delphi  - mezivrstva array of const, [23] v různých implementacích v C++ [24]  - přetížení operací , v C + + 20  - variabilní šablony. Kromě toho formáty ( %datd %f.) neurčují typ argumentu, ale pouze výstupní formát, takže změna typu argumentu může způsobit nouzový stav nebo narušit logiku na vysoké úrovni (například „přerušit“ rozložení tabulky) - ale nekazí paměť.

Nedostatek standardizace

Problém zhoršuje nedostatečná standardizace formátovacích řetězců v různých kompilátorech: například dřívější verze knihoven Microsoftu nepodporovaly "%lld"(museli jste zadat "%I64d"). Stále existuje rozdělení mezi Microsoft a GNU podle typu size_t: %Iuprvní a %zudruhý. GNU C nevyžaduje swprintfmaximální délku řetězce ve funkci (musíte napsat snwprintf).

Neschopnost přeskupit argumenty

Funkce rodiny printfjsou vhodné pro lokalizaci softwaru : například je jednodušší překládat «You hit %s instead of %s.»než úryvky řetězců «You hit »a « instead of ». «.»Ale i zde je problém: není možné přeskupit substituované řetězce na místech, abyste získali: «Вы попали не в <2>, а в <1>.».

Rozšíření printfpoužívaná v Oracle Java a Embarcadero Delphi stále umožňují přeskupit argumenty.

utilita printf

V rámci standardu POSIX je popsán obslužný program printf , který formátuje argumenty podle příslušného vzoru, podobně jako funkce printf .

Nástroj má následující formát volání: , kde printf format [argument …]

  • format  je formátovací řetězec, podobný syntaxi jako formátovací řetězec funkce printf .
  • argument  je seznam argumentů (0 nebo více) zapsaných ve formě řetězce.

Příklady implementace

Příklad 1 C (programovací jazyk)

#include <stdio.h> #include <locale.h> #define PI 3.141593 int main () { setlocale ( LC_ALL , "RUS" ); int číslo = 7 ; plovoucí koláče = 12,75 ; int cost = 7800 ; printf ( "%d soutěžících snědlo %f třešňových koláčů. \n " , počet , koláče ); printf ( "Hodnota pi je %f \n " , PI ); printf ( "Sbohem! Vaše umění stojí příliš mnoho (%c%d) \n " , '$' , 2 * cena ); návrat 0 ; }

Příklad 2 C (programovací jazyk)

#include <stdio.h> #define STRÁNEK 959 int main () { printf ( "*%d* \n " , STRÁNKY ); printf ( "*%2d* \n " , STRÁNKY ); printf ( "*%10d* \n " , STRÁNKY ); printf ( "*%-10d* \n " , STRÁNKY ); návrat 0 ; } /* Výsledek: *959* *959* * 959* *959 * */

Příklad 3 C (programovací jazyk)

#include <stdio.h> #define BLURB "Autentická imitace!" int main () { const double RENT = 3852,99 ; printf ( "*%8f* \n " , PRONÁJEM ); printf ( "*%e* \n " , PRONÁJEM ); printf ( "*%4.2f* \n " , PRONÁJEM ); printf ( "*%3.1f* \n " , PRONÁJEM ); printf ( "*%10.3f* \n " , PRONÁJEM ); printf ( "*%10.3E* \n " , PRONÁJEM ); printf ( "*%+4.2f* \n " , PRONÁJEM ); printf ( "%x %X %#x \n " , 31 , 31 , 31 ); printf ( "**%d**% d% d ** \n " , 42 , 42 , -42 ); printf ( "**%5d**%5.3d**%05d**%05.3d** \n " , 6 , 6 , 6 , 6 ); printf ( " \n " ); printf ( "[%2s] \n " , BLURB ); printf ( "[%24s] \n " , BLURB ); printf ( "[%24.5s] \n " , BLURB ); printf ( "[%-24.5s] \n " , BLURB ); návrat 0 ; } /* výsledek *3852,990000* *3,852990e+03* *3852,99* *3853,0* * 3852,990* * 3,853E+03* *+3852,99* 1f 1F 0x1f **** **042** **042 ** **00006** 006** [Autentická imitace!] [Autentická imitace!] [Authe] [Authe ] */

Odkazy

  1. Stručný popis jazyka BCPL . Získáno 16. prosince 2006. Archivováno z originálu 9. prosince 2006.
  2. Jazyková příručka B Archivována 6. července 2006.
  3. Popis funkce sprintf v dokumentaci Perlu . Získáno 12. ledna 2007. Archivováno z originálu 14. ledna 2007.
  4. Popis operátoru formátování pro typy řetězců v Pythonu Archivováno 9. listopadu 2006.
  5. Popis funkce printf v PHP . Získáno 23. října 2006. Archivováno z originálu 6. listopadu 2006.
  6. 1 2 Popis funkce java.io.PrintStream.printf() v Javě 1.5 . Získáno 12. ledna 2007. Archivováno z originálu 13. ledna 2007.
  7. Popis funkce printf v dokumentaci Ruby . Získáno 3. prosince 2006. Archivováno z originálu 5. prosince 2006.
  8. Popis funkce string.format v dokumentaci Lua . Datum přístupu: 14. ledna 2010. Archivováno z originálu 15. listopadu 2013.
  9. Popis funkce formátování v dokumentaci TCL . Získáno 14. dubna 2008. Archivováno z originálu 4. července 2007.
  10. Popis vzoru řetězce pro printf v dokumentaci GNU Octave . Získáno 3. prosince 2006. Archivováno z originálu 27. října 2006.
  11. Popis printf v dokumentaci Maple{{subst:AI}}
  12. R. Fourer, D. M. Gay a B. W. Kernighan. AMPL: Modelovací jazyk pro matematické programování, 2. vyd. Pacific Grove, CA: Brooks/Cole--Thomson Learning, 2003.
  13. Referenční příručka GNU Emacs Lisp, Formátovací řetězce Archivováno 27. září 2007 na Wayback Machine
  14. Popis modulu Printf v dokumentaci OCaml . Získáno 12. ledna 2007. Archivováno z originálu 13. ledna 2007.
  15. Popis modulu Printf v dokumentaci Haskell . Získáno 23. června 2015. Archivováno z originálu 23. června 2015.
  16. std::println! - Rez . doc.rust-lang.org. Získáno 24. července 2016. Archivováno z originálu 18. srpna 2016.
  17. formát . www.freepascal.org. Získáno 7. prosince 2016. Archivováno z originálu dne 24. listopadu 2016.
  18. fmt - The Go Programming Language . golang.org. Získáno 25. března 2020. Archivováno z originálu dne 4. dubna 2020.
  19. §7.19.6.1 ISO/IEC 9899:TC2
  20. § 7.11.1.1 ISO/IEC 9899:TC2, LC_NUMERIC definuje zejména formu reprezentace oddělovače desetinných míst.
  21. Popis zranitelnosti Printf, Robert C. Seacord: Bezpečné kódování v C a C++. Addison Wesley, září 2005. ISBN 0-321-33572-4
  22. Popis problémů portování aplikací z 32 na 64 bitovou architekturu . Získáno 14. prosince 2006. Archivováno z originálu 8. března 2007.
  23. System.SysUtils.Format Archivováno 11. ledna 2013 na Wayback Machine 
  24. Například boost::formatdokumentace archivovaná 26. března 2013 na Wayback Machine 

Zdroje

  • printf , fprintf , snprintf , vfprintf , vprintf , vsnprintf , vsprintf v ISO/IEC 9899:TC2 (ISO C) [3]
  • printf , fprintf , sprintf , snprintf ve standardu Single Unix [4]
  • vprintf , vfprintf , vsprintf , vsnprintf ve standardu POSIX [5]
  • wprintf , swprintf , wprintf ve standardu POSIX [6]
  • vfwprintf , vswprintf , vwprintf ve standardu POSIX [7]
  • wsprintf na MSDN [8]
  • wvnsprintf na MSDN [9]
  • wnsprintf na MSDN [10]
  • wvsprintf na MSDN [11]
  • wnsprintf na MSDN [12]
  • asprintf , vasprintf v manuálových stránkách na Linuxu [13] , v dokumentaci libc [14]
  • Popis syntaxe formátovacího řetězce naleznete v příručce libc [15] .
  • Popis formátovacího řetězce v dokumentaci pro Microsoft Visual Studio 2005 [16]
  • Popis funkce register_printf_function [17] , [18]
  • Programovací jazyk C. Přednášky a cvičení. Autor: Stephen Prata. ISBN 978-5-8459-1950-2 , 978-0-321-92842-9; 2015

Viz také