printf (az angol print formatted , "formázott nyomtatás" szóból) - a szabványos vagy jól ismert kereskedelmi könyvtárak, illetve egyes programozási nyelvek formázott kimenetre használt beépített operátorainak függvény- vagy módszercsaládjának általánosított neve - kimenet különböző típusú , adott sablon szerint formázott értékfolyamokhoz . Ezt a sablont egy speciális szabályok szerint összeállított karakterlánc (formátumkarakterlánc) határozza meg.
Ennek a családnak a legfigyelemreméltóbb tagja a printf függvény , valamint számos más függvény, amely printfa C szabványkönyvtár nevéből származik (amely szintén a C++ és Objective-C szabványkönyvtárak része ).
A UNIX operációs rendszerek családja rendelkezik egy printf segédprogrammal is , amely a formázott kimenet ugyanazokat a célokat szolgálja.
A Fortran FORMAT operátora egy ilyen függvény korai prototípusának tekinthető . A karakterlánc-vezérelt következtetési függvény a C nyelv előfutáraiban ( BCPL és B ) jelent meg. A C standard könyvtár specifikációjában megkapta a legismertebb formáját (jelzőkkel, szélességgel, pontossággal és mérettel). A kimeneti sablon karakterláncának szintaxisát (ezt néha formátumkarakterláncnak , formátumkarakterláncnak vagy formátumkarakterláncnak nevezik ) később más programozási nyelvek is használták (a nyelvek jellemzőinek megfelelő változatokkal). Általában ezeknek a nyelveknek a megfelelő funkcióit printf -nek és/vagy származékainak is nevezik.
Néhány újabb programozási környezet (például a .NET ) szintén használja a formátum karakterlánc-vezérelt kimenet fogalmát, de eltérő szintaxissal.
A Fortrannak már voltak operátoraim, amelyek formázott kimenetet biztosítottak. A WRITE és PRINT utasítások szintaxisa tartalmazott egy címkét, amely egy formátumspecifikációt tartalmazó, nem végrehajtható FORMAT utasításra utalt . A specifikációk az operátor szintaxisának részét képezték, és a fordító azonnal képes volt olyan kódot generálni, amely közvetlenül végrehajtja az adatformázást, ami a legjobb teljesítményt biztosította az akkori számítógépeken. Ennek ellenére a következő hátrányok voltak:
A jövőbeli printf függvény első prototípusa az 1960 -as években jelent meg a BCPL nyelven . A WRITEF függvény egy formátumkarakterláncot vesz fel, amely a karakterlánc változóban magától az adattól külön határozza meg az adattípust (a típust zászlók, szélesség, pontosság és méret mezők nélkül adták meg, de már egy százalékjel előzte meg ). [1] A formátumkarakterlánc fő célja argumentumtípusok átadása volt (a statikus gépeléssel rendelkező programozási nyelvekben az átadott argumentum típusának meghatározása egy nem rögzített formális paraméterlistával rendelkező függvénynél bonyolult és nem hatékony mechanizmust igényel típusinformáció átadására általános esetben). Maga a WRITEF függvény a kimenet egyszerűsítését szolgálta: a WRCH ( karakter kiírása), WRITES (karakterláncot), WRITEN , WRITED , WRITEOCT , WRITEHEX (számok különböző formájú kimenete) függvénykészlet helyett egyetlen hívás használták, amelyben lehetséges volt "csak szöveget" a kimeneti értékekkel átlapolni. %
Az 1969 -ben ezt követő Bee nyelv már a printf nevet használta egy egyszerű formátumú karakterlánccal (hasonlóan a BCPL -hez ), amely három lehetséges típus és két számábrázolás közül csak egyet adott meg: decimális ( %d), oktális ( %o), karakterláncok ( %s) és karakterek ( %c), és ezekben a függvényekben a kimenet formázásának egyetlen módja az volt, hogy karaktereket adunk a változó értékének kimenete előtt és után. [2]
A C nyelv első változatának bevezetése ( 1970 ) óta a printf család a fő formátumkimeneti eszközzé vált. A formátumkarakterlánc egyes függvényhívásokkal történő elemzésének költségét elfogadhatónak ítélték, és az egyes típusokhoz külön-külön alternatív hívások nem kerültek be a könyvtárba. A funkcióspecifikáció mindkét létező , 1990 -ben és 1999 -ben kiadott nyelvi szabványban szerepelt . Az 1999-es specifikáció néhány újítást tartalmaz az 1990-es specifikációból.
A C++ nyelv a szabványos C könyvtárat használja (az 1990-es szabvány szerint), beleértve a teljes printf családot .
Alternatív megoldásként a C++ szabványos könyvtár folyam bemeneti és kimeneti osztályokat biztosít. Ennek a könyvtárnak a kimeneti utasításai típusbiztosak, és nem igényelnek formátum karakterlánc-elemzést minden egyes meghívásakor. Sok programozó azonban továbbra is a printf családot használja , mivel a kimeneti sorrend velük általában kompaktabb, és a használt formátum lényege világosabb.
Az Objective-C egy meglehetősen "vékony" kiegészítő a C-hez, és a rajta lévő programok közvetlenül használhatják a printf család funkcióit .
A C-n és származékain (C++, Objective-C) kívül sok más programozási nyelv is használja a printf-szerű formátumú karakterlánc-szintaxist:
Ezenkívül a legtöbb UNIX-szerű rendszerben található printf segédprogramnak köszönhetően a printf számos shell szkriptben használatos ( sh , bash , csh , zsh stb. esetén).
Néhány újabb nyelv és programozási környezet is használja a formátum karakterlánc-vezérelt kimenet fogalmát, de eltérő szintaxissal.
Például a .Net Core Class Library (FCL) a System.String.Format , System.Console.Write és System.Console.WriteLine metóduscsaláddal rendelkezik , amelyek némelyik túlterhelése egy formátum karakterlánc szerint adja ki az adatokat. Mivel az objektumtípusokról teljes információ áll rendelkezésre a .Net futtatókörnyezetben, nem szükséges ezeket az információkat a formátumkarakterláncban átadni.
Minden függvény nevében szerepel a printf szár . A függvény neve előtti előtagok jelentése:
Minden függvény egy formátum karakterláncot vesz fel az egyik paraméterként ( format ) (a karakterlánc szintaxisának leírása alább). A kiírt (kinyomtatott) karakterek számát adja vissza , a végén lévő null karaktert nem számítva . A formázott kimenethez adatokat tartalmazó argumentumok számának legalább annyinak kell lennie, amennyi a formátum karakterláncban szerepel. Az "extra" argumentumokat figyelmen kívül hagyja.
Az n családfüggvény ( snprintf , vsnprintf ) visszaadja a kinyomtatott karakterek számát, ha az n paraméter (a nyomtatandó karakterek számának korlátozása) elég nagy lenne. Egybájtos kódolás esetén a visszatérési érték a karakterlánc kívánt hosszának felel meg (nem számítva a végén lévő null karaktert).
Az s család függvényei ( sprintf , snprintf , vsprintf , vsnprintf ) első paraméterükként egy mutatót vesznek fel arra a memóriaterületre, ahová az eredményül kapott karakterláncot írjuk. Azok a függvények, amelyek nem korlátozzák a kiírt karakterek számát, nem biztonságos funkciók, mivel puffertúlcsordulási hibához vezethetnek, ha a kimeneti karakterlánc nagyobb, mint a kimenethez lefoglalt memória mérete.
Az f család függvényei bármilyen nyitott adatfolyamba írnak egy karakterláncot (az adatfolyam paramétert ), különösen a szabványos kimeneti folyamokra ( stdout , stderr ). fprintf(stdout, format, …)-vel egyenértékű printf(format, …).
A v család függvényei az argumentumokat nem változó számú argumentumként veszik fel (mint az összes többi printf függvény), hanem listának va list . Ebben az esetben a függvény meghívásakor a va end makró nem kerül végrehajtásra.
A w (első karakter) család függvényei az s függvénycsalád korlátozott Microsoft implementációi : wsprintf , wnsprintf , wvsprintf , wvnsprintf . Ezek a funkciók a user32.dll és shlwapi.dll dinamikus könyvtárakban vannak megvalósítva ( n függvény). Nem támogatják a lebegőpontos kimenetet, a wnsprintf és a wvnsprintf pedig csak balra igazított szöveget támogat.
A w család funkciói ( wprintf , swprintf ) a többbyte-os kódolások támogatását valósítják meg, ennek a családnak minden funkciója többbyte-os karakterláncokra mutató mutatókkal ( wchar_t ) működik.
Az a család függvényei ( asprintf , vasprintf ) a malloc függvény segítségével foglalnak le memóriát a kimeneti karakterlánchoz , a memória felszabadul a hívási eljárás során, a függvény végrehajtása közbeni hiba esetén a memória nem kerül lefoglalásra.
Visszatérési érték: negatív érték — hibajel; ha sikeres, akkor a függvények visszaadják az írt/kimeneti bájtok számát (a végén a null byte-ot figyelmen kívül hagyva), az snprintf függvény pedig azt a bájtok számát írja ki, ami akkor lenne kiírva, ha n elég nagy lenne.
Az snprintf meghívásakor n lehet nulla (ez esetben s lehet null pointer ), ilyenkor nem történik írás, a függvény csak a helyes visszatérési értéket adja vissza.
C-ben és C++-ban a formátum karakterlánc null-végződésű karakterlánc. A formátum-meghatározók kivételével minden karaktert a rendszer változatlanul átmásol a kapott karakterláncba. A formátummeghatározó elejének standard jele a karakter %( Percent sign ), magának a jelnek a megjelenítéséhez %annak megkettőzését használjuk %%.
A formátum megadása így néz ki:
% [ zászlók ][ szélesség ][ . pontosság ][ méret ] típusA szükséges összetevők a formátummeghatározó kezdő karaktere ( %) és a típus .
FlagsJel | Aláírási név | Jelentése | Ennek a jelnek a hiányában | jegyzet |
---|---|---|---|---|
- | mínusz | a kimeneti érték balra van igazítva a minimális mezőszélességen belül | jobbra | |
+ | egy plusz | mindig adjon meg egy jelet (plusz vagy mínusz) a megjelenített decimális számértékhez | csak negatív számok esetén | |
tér | Tegyen szóközt az eredmény elé, ha az érték első karaktere nem előjel | A kimenet számmal kezdődhet. | A + karakter elsőbbséget élvez a szóköz karakterrel szemben. Csak előjeles decimális értékekhez használatos. | |
# | rács | értékkiadás „alternatív formája”. | Ha számokat hexadecimális vagy oktális formátumban ad ki, a számot egy formátumjellemző (0x vagy 0) előzi meg. | |
0 | nulla | töltse be a mezőt a jellel az escape szekvencia szélessége mezőben megadott szélességre0 | betét szóközökkel | A d , i , o , u , x , X , a , A , e , E , f , F , g , G típusokhoz használatos . A d , i , o , u , x , X típusoknál a pontosság megadása esetén ez a jelző figyelmen kívül marad. Más típusok esetében a viselkedés nem definiált.
Ha mínusz '-' jelző van megadva, akkor ezt a jelzőt is figyelmen kívül hagyja. |
A Width (tizedes vagy csillag karakter ) a mező minimális szélességét adja meg (beleértve a számok előjelét is). Ha az értékmegjelenítés nagyobb, mint a mező szélessége, akkor a bejegyzés a mezőn kívül esik (például a %2i 100-as érték esetén három karakteres mezőértéket ad), ha az értékábrázolás kisebb, mint a megadott szám, akkor ez (alapértelmezés szerint) szóközökkel lesz kitöltve a bal oldalon, a viselkedés a többi beállított jelzőtől függően változhat. Ha egy csillag van megadva szélességként, akkor a mező szélességét az argumentumlistában a kimeneti érték előtt kell megadni (például printf( "%0*x", 8, 15 );szöveget jelenít meg 0000000f). Ha negatív szélességmódosítót adunk meg ilyen módon, akkor a - jelzőt beállítottnak tekintjük , és a szélesség módosító értékét abszolút értékre állítjuk.
Pontosság módosítóA pontosság egy pont, amelyet egy tizedes szám vagy egy csillag ( * ) követ, ha nincs szám vagy csillag (csak egy pont van jelen), akkor a szám nullának számít. A pont a pontosság jelzésére szolgál még akkor is, ha vessző jelenik meg a lebegőpontos számok kiírásakor.
Ha a pont után csillag karakter van megadva, akkor a formátum karakterlánc feldolgozása során a mező értéke az argumentumlistából kerül kiolvasásra. (Ugyanakkor, ha a csillag karakter a szélesség mezőben és a precíziós mezőben is szerepel, először a szélesség jelenik meg, majd a pontosság, és csak azután a kimenet értéke). Például printf( "%0*.*f", 8, 4, 2.5 );megjeleníti a szöveget 002.5000. Ha negatív precíziós módosító van így megadva, akkor nincs precíziós módosító. [19]
MéretmódosítóA méret mező lehetővé teszi a függvénynek átadott adatok méretének megadását. Ennek a mezőnek a szükségességét a függvénynek tetszőleges számú paraméter átadásának sajátosságai magyarázzák a C nyelvben: a függvény nem tudja "önállóan" meghatározni az átvitt adatok típusát és méretét, így a paraméterek típusáról és azok méretéről szóló információ. a pontos méretet kifejezetten meg kell adni.
Figyelembe véve a méretmeghatározások hatását az egész számok formázására, meg kell jegyezni, hogy a C és C++ nyelvekben előjeles és előjel nélküli egész típusok párjainak láncolata létezik, amelyek a méretek nem csökkenő sorrendjében a következőképpen rendezve:
aláírt típus | Aláíratlan típus |
---|---|
aláírt char | előjel nélküli char |
aláírt rövid ( rövid ) | unsigned short int ( unsigned short ) |
aláírt int ( int ) | unsigned int ( unsigned ) |
hosszú int ( hosszú ) | unsigned long int ( unsigned long ) |
hosszú hosszú int ( hosszú hosszú ) | unsigned long long int ( unsigned long long ) |
A típusok pontos mérete nem ismert, kivéve az előjeles char és az előjel nélküli char típusokat .
A páros előjeles és előjel nélküli típusok mérete megegyezik, és a mindkét típusban reprezentálható értékek ugyanazt a reprezentációt tartalmazzák.
A char típus mérete megegyezik az előjeles és az előjel nélküli karaktertípusokkal , és megosztja a reprezentálható értékek halmazát ezen típusok egyikével. Feltételezzük továbbá, hogy a char egy másik neve ezen típusok egyikének; egy ilyen feltételezés a jelen megfontolás szempontjából elfogadható.
Ezenkívül a C a _Bool típussal rendelkezik , míg a C++ a bool típusú .
Amikor argumentumokat adunk át egy függvénynek, amelyek nem felelnek meg a függvény prototípusának formális paramétereinek (melyek mindegyik argumentum kimeneti értékeket tartalmaz), ezek az argumentumok szabványos előléptetéseken mennek keresztül , nevezetesen:
Így a printf függvények nem vehetnek fel float , _Bool vagy bool típusú argumentumokat, illetve int vagy unsigned típusú integer típusú argumentumokat .
A használt méretmeghatározók készlete a típusleírótól függ (lásd alább).
meghatározó | %d, %i, %o, %u, %x,%X | %n | jegyzet |
---|---|---|---|
hiányzó | int vagy unsigned int | mutató int | |
l | long int vagy unsigned long int | mutató hosszú int | |
hh | Az argumentum int vagy unsigned int típusú , de kénytelen beírni: signed char vagy unsigned char | mutató az előjeles karakterre | formálisan a C nyelven az 1999-es szabvány óta, a C++-ban pedig a 2011-es szabvány óta létezik. |
h | Az argumentum int vagy unsigned int típusú , de kénytelen beírni a short int vagy unsigned short int | mutató rövid int | |
ll | long long int vagy unsigned long long int | pointer long long int | |
j | intmax_t vagy uintmax_t | mutató intmax_t | |
z | size_t (vagy mérettel egyenértékű aláírt típus) | mutasson egy előjeles típusra , amely méretében megegyezik a size_t mérettel | |
t | ptrdiff_t (vagy ezzel egyenértékű, előjel nélküli típus) | mutasson a ptrdiff_t -re | |
L | __int64 vagy aláírás nélküli __int64 | mutató az __int64 -re | Borland Builder 6 esetén (a specifikátor ll32 bites számot vár) |
A specifikációk hés hha szabványos típusú promóciók kompenzálására szolgálnak az aláírt típusról az aláíratlan típusra való átmenetekkel együtt, vagy fordítva.
Vegyünk például egy C-megvalósítást, ahol a char típus előjeles és 8 bites, az int típusé 32 bites, és a negatív egészek kódolásának egy további módja van.
char c = 255 ; printf ( "%X" , c );Egy ilyen hívás kimenetet produkál FFFFFFFF, ami nem biztos, hogy azt a programozó várta. Valójában c értéke (char)(-1) , a típuspromóció után pedig -1 . A formátum alkalmazása %Xa megadott értéket előjel nélküliként értelmezi, azaz 0xFFFFFFFF .
char c = 255 ; printf ( "%X" , ( unsigned char ) c ); char c = 255 ; printf ( "%hhX" , c );Ez a két hívás ugyanazt a hatást fejti ki, és a kimenetet hozza létre FF. Az első opció lehetővé teszi az előjel-szorzás elkerülését a típus népszerűsítésekor, a második pedig már a printf függvényben "belül" kompenzálja azt .
meghatározó | %a, %A, %e, %E, %f, %F, %g,%G |
---|---|
hiányzó | kettős |
L | hosszú dupla |
meghatározó | %c | %s |
---|---|---|
hiányzó | Az argumentum int vagy unsigned int típusú , de kénytelen beírni a char parancsot | char* |
l | Az argumentum wint_t típusú , de kénytelen beírni a wchar_t | wchar_t* |
A típus nem csak az érték típusát jelzi (a C programozási nyelv szempontjából), hanem a kimeneti érték konkrét ábrázolását is (például a számok decimális vagy hexadecimális formában jeleníthetők meg). Egyetlen karakterként írva. Más mezőkkel ellentétben kötelező. Az egyetlen escape szekvencia maximális támogatott kimeneti mérete szabvány szerint legalább 4095 karakter; a gyakorlatban a legtöbb fordítóprogram lényegesen nagyobb adatmennyiséget támogat.
Típusértékek:
Az aktuális területi beállítástól függően vessző és pont (és esetleg egy másik szimbólum) is használható a lebegőpontos számok megjelenítésekor. A printf viselkedését a szám tört és egész részét elválasztó karakterhez képest a használatban lévő terület (pontosabban az LC NUMERIC változó ) határozza meg. [húsz]
Speciális makrók egész adattípusú álnevek kiterjesztett halmazáhozA Second C szabvány (1999) kiterjesztett álneveket biztosít az int N _t , uint N _t , int_least N _t , uint_least N _t , int_fast N _t , uint_fast N _t (ahol N a szükséges bitmélység), intptr_t egész adattípusokhoz. , uintptr_t , intmax_t , uintmax_t .
Ezen típusok mindegyike egyezik a szabványos beépített egész típusokkal, vagy nem. Formálisan a hordozható kód írásakor a programozó nem tudja előre, hogy melyik szabványos vagy kiterjesztett méretű specifikációt kell alkalmaznia.
int64_t x = 100000000000 ; int szélesség = 20 ; printf ( "%0*lli" , szélesség , x ); Rossz, mert lehet, hogy az int64_t nem ugyanaz, mint a long long int .Annak érdekében, hogy az ilyen típusú objektumok vagy kifejezések értékeit hordozható és kényelmes módon lehessen következtetni, a megvalósítás mindegyik típushoz meghatároz egy makrót, amelyek értékei a méret- és típusspecifikációkat kombináló karakterláncok.
A makrók nevei a következők:
Egy pár aláírt és előjel nélküli típus | Makró neve |
---|---|
int N_t és uint N_t _ _ | PRITN |
int_least N _t és uint_least N _t | PRITLEASTN |
int_fastN_t és uint_fastN_t _ _ _ _ | PRITFASTN |
intmax_t és uintmax_t | PRITMAX |
intptr_t és uintptr_t | PRITPTR |
Itt T található a következő típusspecifikációk egyike: d, i, u, o, x, X.
int64_t x = 100000000000 ; int szélesség = 20 ; printf ( "%0*" PRIi64 , szélesség , x ); Az int64_t típusú érték helyes kiadása C nyelven.Észreveheti, hogy az intmax_t és uintmax_t típusok szabványos méretmeghatározóval rendelkeznek j, így a makró valószínűleg mindig a következőképpen van megadva . PRITMAX"jT"
A Single UNIX szabvány szerint (amely gyakorlatilag egyenértékű a POSIX szabvánnyal ) a printf következő kiegészítései vannak meghatározva az ISO C-hez képest, az XSI (X/Open System Interface) kiterjesztés alatt:
A GNU C Library ( libc ) a következő kiterjesztéseket adja hozzá:
A GNU libc támogatja az egyéni típusú regisztrációt, lehetővé téve a programozó számára, hogy meghatározza saját adatstruktúráihoz a kimeneti formátumot. Új típus regisztrálásához használja a függvényt
int register_printf_function (int type, printf_function handler-function, printf_arginfo_function arginfo-function), ahol:
Az új típusok meghatározása mellett a regisztráció lehetővé teszi a meglévő típusok (például s , i ) újradefiniálását.
Microsoft Visual CA Microsoft Visual Studio a C/C++ programozási nyelvekhez a printf specifikáció formátumában (és egyéb családi funkciók) a következő bővítményeket biztosítja:
mező értéke | típusú |
---|---|
I32 | aláírt __int32 , aláíratlan __int32 |
I64 | aláírt __int64 , aláíratlan __int64 |
én | ptrdiff_t , size_t |
w | l -vel egyenértékű karakterláncok és karakterek esetén |
A Maple matematikai környezetnek van egy printf függvénye is, amely a következő tulajdonságokkal rendelkezik:
FormázásPélda:
> printf("%a =%A", `+`, `+`); `+` = + > printf("%a =%m", `+`, `+`); `+` = I"+f*6"F$6#%(builtinGF$"$Q"F$F$F$F"%*protectedG KövetkeztetésA Maple fprintf függvénye vagy egy fájlleírót (az fopen által visszaadott), vagy egy fájlnevet vesz fel első argumentumként. Ez utóbbi esetben a névnek „szimbólum” típusúnak kell lennie, ha a fájlnév pontokat tartalmaz, akkor azt backtick-be kell zárni, vagy konvertálni (fájlnév, szimbólum) függvénnyel.
A printf család függvényei az argumentumok listáját és azok méretét külön paraméterként veszik (a formátum stringben). A formátum karakterlánc és az átadott argumentumok közötti eltérés előre nem látható viselkedéshez, verem sérüléséhez, tetszőleges kódfuttatáshoz és a dinamikus memóriaterületek megsemmisüléséhez vezethet. A család számos funkcióját "unsafe"-nek ( angolul unsafe ) nevezik, mivel még csak elméletileg sem képesek megvédeni a helytelen adatok ellen.
Ezenkívül az s család függvényei ( n nélkül , például sprintf , vsprintf ) nem korlátozzák az írt karakterlánc maximális méretét, és puffertúlcsordulási hibához vezethetnek (ha az adatokat a lefoglalt memóriaterületen kívül írják).
A hívási konvenció részeként a cdeclveremtisztítást a hívó funkció végzi. A printf meghívásakor az argumentumok (vagy a rájuk mutatók) a beírásuk sorrendjében (balról jobbra) kerülnek elhelyezésre. A formázási karakterlánc feldolgozása közben a printf függvény argumentumokat olvas be a veremből. A következő helyzetek lehetségesek:
A C nyelvi specifikációk csak két helyzetet írnak le (normál művelet és extra argumentumok). Minden más helyzet hibás, és meghatározatlan programviselkedéshez vezet (a valóságban tetszőleges eredményekhez vezet, egészen a nem tervezett kódszakaszok végrehajtásáig).
Túl sok érvHa túl sok argumentumot ad át, a printf függvény beolvassa a formátum karakterlánc helyes feldolgozásához szükséges argumentumokat, és visszatér a hívó függvényhez. A hívó függvény a specifikációnak megfelelően törli a veremből a hívott függvénynek átadott paramétereket. Ebben az esetben az extra paraméterek egyszerűen nem kerülnek felhasználásra, és a program változtatás nélkül folytatódik.
Nincs elég érvHa a printf meghívásakor kevesebb argumentum van a veremben, mint amennyi a formázási karakterlánc feldolgozásához szükséges, akkor a hiányzó argumentumok kiolvasásra kerülnek a veremből, annak ellenére, hogy tetszőleges adat van a veremben (nem releváns a printf munkájához ). . Ha az adatfeldolgozás „sikeres” volt (azaz nem fejezte be a programot, nem lógott le vagy nem írt a verembe), a hívó függvényhez való visszatérés után a veremmutató értéke visszaáll az eredeti értékére, és a a program folytatódik.
Az "extra" veremértékek feldolgozásakor a következő helyzetek lehetségesek:
Formálisan minden eltérés az argumentum típusa és az elvárás között a program definiálatlan viselkedését okozza. A gyakorlatban számos olyan eset van, amelyek a programozási gyakorlat szempontjából különösen érdekesek:
Más esetek általában nyilvánvalóan helytelen viselkedéshez vezetnek, és könnyen észlelhetők.
Egész vagy lebegőpontos argumentumméret nem egyezikEgész argumentum esetén (egész formátum specifikációval) a következő helyzetek lehetségesek:
Valódi argumentum esetén (valós formátum specifikációval), bármilyen méretbeli eltérés esetén a kimeneti érték általában nem egyezik az átadott értékkel.
Általános szabály, hogy ha valamelyik argumentum mérete hibás, akkor az összes további argumentum helyes feldolgozása lehetetlenné válik, mivel az argumentumok mutatójába hiba kerül be. Ez a hatás azonban ellensúlyozható a verem értékeinek igazításával.
Értékek igazítása a verembenSzámos platform rendelkezik egész számok és/vagy valós értékek igazítási szabályaival, amelyek megkövetelik (vagy javasolják), hogy azokat a méretük többszörösének megfelelő címeken helyezzék el. Ezek a szabályok a függvényargumentumok veremben való átadására is vonatkoznak. Ebben az esetben a várt és a tényleges paraméterek típusaiban előforduló számos eltérés észrevétlen marad, ami a megfelelő program illúzióját keltheti.
uint32_t a = 1 ; uint64_t b = 2 , c = 3 ; printf ( "%" PRId64 "%" PRId64 "%" PRId64 , b , a , c ); Ebben a példában a tényleges atípusparaméterhez uint32_térvénytelen formátumspecifikáció %"PRId64"tartozik a típushoz uint64_t. Egyes 32 bites típusú platformokon azonban intaz elfogadott bájtsorrendtől és a verem növekedési irányától függően a hiba észrevétlen maradhat. A tényleges bés paraméterek cegy olyan címhez lesznek igazítva, amely a méretük többszöröse (a méret kétszerese a). Az értékek „között” pedig aegy b32 bites üres (általában nullázott) hely marad; az anyagjegyzék feldolgozása során a %"PRId64"32 bites érték aezzel a szóközzel együtt egyetlen 64 bites értékként értelmeződik.Ilyen hiba váratlanul megjelenhet a programkód másik platformra történő portolásakor, a fordító vagy a fordítási mód megváltoztatásakor.
Lehetséges méreteltérésA C és C++ nyelvek definíciói csak az adattípusok méretére és megjelenítésére vonatkozó legáltalánosabb követelményeket írják le. Ezért sok platformon néhány formálisan eltérő adattípus ábrázolása megegyezik. Emiatt bizonyos típusú eltérések hosszú ideig észrevétlenek maradnak.
Például a Win32 platformon általánosan elfogadott, hogy a és típusok mérete intmegegyezik long int(32 bit). Így a printf("%ld", 1)vagy a hívás printf("%d", 1L)„helyesen” végrehajtásra kerül.
Ilyen hiba váratlanul megjelenhet a programkód másik platformra történő portolásakor, a fordító vagy a fordítási mód megváltoztatásakor.
A C++ nyelvű programok írásakor ügyelni kell az egész típusú álnevekkel deklarált változók értékeinek származtatására, különösen a size_t, és ptrdiff_t; a C++ szabványkönyvtár formális definíciója az első C szabványra (1990) utal. A második C szabvány (1999) méretspecifikációkat határoz meg a típusokhoz size_tés számos más típushoz hasonló objektumokkal való használatra. ptrdiff_tSok C++ implementáció is támogatja őket.
méret_t s = 1 ; printf ( "%u" , s ); Ez a példa egy olyan hibát tartalmaz, amely olyan platformokon fordulhat elő, sizeof (unsigned int)ahol sizeof (size_t). méret_t s = 1 ; printf ( "%zu" , s ); A típusobjektum értékére a helyes következtetés a size_tC nyelv. Típushiba, ha a méret megegyezikHa az átadott argumentumok azonos méretűek, de eltérő típusúak, akkor a program gyakran "majdnem helyesen" fog futni (nem okoz memóriaelérési hibákat), bár a kimeneti érték valószínűleg értelmetlen. Meg kell jegyezni, hogy a páros egész típusok (előjeles és előjel nélküli) keverése megengedett, nem okoz definiálatlan viselkedést, és néha szándékosan használják a gyakorlatban.
Formátumspecifikáció használatakor a rendszer a karakterlánc címétől %seltérő egész, valós vagy mutató típusú argumentumértéket char*értelmezi a karakterlánc címeként. Ez a cím általánosságban tetszőlegesen mutathat egy nem létező vagy hozzáférhetetlen memóriaterületre, ami memóriaelérési hibához vezet, vagy olyan memóriaterületre, amely nem tartalmaz sort, ami értelmetlen kimenetet eredményez, esetleg nagyon nagy .
Mivel a printf (és a család többi funkciója) változtatás nélkül tudja kiadni a formátum karakterlánc szövegét, ha nem tartalmaz escape szekvenciákat, akkor lehetséges a parancs általi szövegkiadás
printf(text_to_print);
Ha a text_to_print külső forrásból származik (fájlból olvasható , amelyet a felhasználótól vagy az operációs rendszertől kaptunk), akkor a százalékjel jelenléte a kapott karakterláncban rendkívül nemkívánatos következményekkel járhat (a program lefagyásáig).
Példa hibás kódra:
printf(" Current status: 99% stored.");
Ez a példa egy „% s” escape szekvenciát tartalmaz, amely az escape szekvencia karaktert (%), egy jelzőt (szóköz) és egy karakterlánc adattípust ( s ) tartalmaz. A függvény, miután megkapta a vezérlőszekvenciát, megpróbálja beolvasni a stringre mutató mutatót a veremből. Mivel nem adtak át további paramétereket a függvénynek, a veremből kiolvasandó érték nincs meghatározva. Az eredményül kapott értéket a rendszer egy null-végződésű karakterláncra mutató mutatóként értelmezi. Egy ilyen "karakterlánc" kimenete tetszőleges memóriakiíratáshoz, memóriaelérési hibához és veremsérüléshez vezethet. Az ilyen típusú sebezhetőséget formátumkarakterlánc - támadásnak nevezik . [21]
A printf függvényt eredmény kiírásakor nem korlátozza a kimeneti karakterek maximális száma. Ha egy hiba vagy tévedés következtében a vártnál több karakter jelenik meg, a legrosszabb, ami történhet, a kép „megsemmisülése” a képernyőn. A printf analógiájával létrehozott sprintf függvényt sem korlátozták a kapott karakterlánc maximális méretében. A „végtelen” termináltól eltérően azonban az alkalmazás által a kapott karakterlánchoz lefoglalt memória mindig korlátozott. Az elvárt határok túllépése esetén pedig más adatstruktúrákhoz tartozó memóriaterületeken (illetve általában nem elérhető memóriaterületeken) történik a rögzítés, ami azt jelenti, hogy szinte minden platformon összeomlik a program. A memória tetszőleges területeire való írás előre nem látható hatásokhoz vezet (amelyek sokkal később jelentkezhetnek, és nem programhiba, hanem felhasználói adatok sérülése formájában). A maximális karakterláncméret korlátozásának hiánya alapvető tervezési hiba a függvény fejlesztése során. Emiatt a sprintf és a vsprintf függvények nem biztonságos állapotúak . Ehelyett kifejlesztette az snprintf , vsnprintf függvényeket , amelyek egy további argumentumot vesznek fel, amely korlátozza a maximális eredő karakterláncot. A jóval később megjelent swprintf függvény (többbyte -os kódolásokhoz) figyelembe veszi ezt a hiányosságot, és egy argumentumot vesz fel a kapott karakterlánc korlátozására. (Ezért nincs snwprintf függvény ).
Példa a sprintf veszélyes hívására :
charbuffer [65536]; char* név = get_user_name_from_keyboard(); sprintf(puffer, "Felhasználónév:%s", név);A fenti kód implicit módon feltételezi, hogy a felhasználó nem fog 65 ezer karaktert beírni a billentyűzeten, és a puffernek "elég kell lennie". De a felhasználó átirányíthatja a bevitelt egy másik programból, vagy még mindig beírhat több mint 65 000 karaktert. Ebben az esetben a memóriaterületek megsérülnek, és a program viselkedése kiszámíthatatlanná válik.
A printf család funkciói C adattípust használnak . Az ilyen típusok mérete és aránya platformonként változhat. Például 64 bites platformokon a választott modelltől ( LP64 , LLP64 vagy ILP64 ) függően az int és long típusok mérete eltérő lehet. Ha a programozó "majdnem helyes"-re állítja a formátum karakterláncot, a kód az egyik platformon működik, a másikon pedig rossz eredményt ad (egyes esetekben adatsérüléshez vezethet).
Például a kód printf( "text address: 0x%X", "text line" );megfelelően működik egy 32 bites platformon ( ptrdiff_t mérete és int mérete 32 bit) és egy 64 bites IPL64 modellen (ahol a ptrdiff_t és az int mérete 64 bit), de 64 bites esetén helytelen eredményt ad. -bites platform egy LP64 vagy LLP64 modellben, ahol a ptrdiff_t mérete 64 bit, az int mérete pedig 32 bit. [22]
Az Oracle Java - ban dinamikus azonosítással burkolt típusokprintf használatosak egy függvény analógjában , [6] az Embarcadero Delphiben - egy köztes réteg , [23] a különböző megvalósításokban C ++ -ban [24] - műveletek túlterhelése , C + -ban + 20 - változó sablonok. Ráadásul a formátumok ( , stb.) nem adják meg az argumentum típusát, hanem csak a kimeneti formátumot, így az argumentum típusának megváltoztatása vészhelyzetet okozhat, vagy megtörheti a magas szintű logikát (pl. a táblázat elrendezése) - de nem rontja el a memóriát. array of const%d%f
A problémát súlyosbítja a formátum karakterláncainak elégtelen szabványosítása a különböző fordítókban: például a Microsoft-könyvtárak korai verziói nem támogatták "%lld"(meg kellett adnia "%I64d"). A Microsoft és a GNU között továbbra is létezik típus szerinti felosztás size_t: %Iuaz előbbi és %zuaz utóbbi. A GNU C nem követeli meg a swprintfmaximális karakterlánchosszt egy függvényben (írnod kell snwprintf).
A családfüggvények kényelmesek printfa szoftverhonosításhoz«You hit %s instead of %s.» : például egyszerűbb lefordítani, mint a karakterlánc-részletek «You hit »és « instead of »a «.». De itt is van egy probléma: lehetetlen a helyettesített karakterláncokat olyan helyekre átrendezni, hogy a következőt kapjuk: «Вы попали не в <2>, а в <1>.».
Az Oracle Java -ban és az Embarcadero Delphibenprintf használt kiterjesztések továbbra is lehetővé teszik az argumentumok átrendezését.
A POSIX szabványon belül le van írva a printf segédprogram , amely a printf függvényhez hasonlóan formázza az argumentumokat a megfelelő minta szerint .
A segédprogram a következő hívásformátummal rendelkezik: , where printf format [argument …]
Unix parancsok | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
|