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í.
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
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 .
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ě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.
Všechny funkce mají ve svém názvu kmen printf . Předpony před názvem funkce znamenají:
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.
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.
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í %%.
Specifikátor formátu vypadá takto:
% [ příznaky ][ šířka ][ . přesnost ][ velikost ] typPožadované součásti jsou počáteční znak specifikátoru formátu ( %) a typ .
VlajkyPodepsat | 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. |
Šíř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řesnostiPř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 velikostiPole 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ě:
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* |
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:
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"
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):
Knihovna GNU C ( libc ) přidává následující rozšíření:
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:
Kromě definování nových typů umožňuje registrace předefinování existujících typů (například s , i ).
Microsoft Visual CMicrosoft Visual Studio pro programovací jazyky C/C++ ve formátu specifikace printf (a další funkce rodiny) poskytuje následující rozšíření:
hodnota pole | Typ |
---|---|
I32 | podepsané __int32 , nepodepsané __int32 |
I64 | podepsané __int64 , nepodepsané __int64 |
já | ptrdiff_t , velikost_t |
w | ekvivalentní l pro řetězce a znaky |
Matematické prostředí Maple má také funkci printf, která má následující vlastnosti:
FormátováníPříklad:
> printf("%a =%A", `+`, `+`); `+` = + > printf("%a =%m", `+`, `+`); `+` = I"+f*6"F$6#%(builtinGF$"$Q"F$F$F$F"%*chráněnoG ZávěrFunkce 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).
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).
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:
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:
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:
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 čárkouPro celočíselný argument (se specifikací celočíselného formátu) jsou možné následující situace:
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íkuMnoho 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 velikostiDefinice 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ě velikostiPokud 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 .
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]
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.
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ěť.
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).
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.
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 …]