printf ( englanniksi print formatated , "muotoiltu tulostus") - yleinen nimi standardien tai tunnettujen kaupallisten kirjastojen funktioiden tai menetelmien perheelle tai joidenkin ohjelmointikielten sisäänrakennetuille operaattoreille, joita käytetään alustettuun tulosteeseen - tulostetaan erityyppisiin arvovirtoihin , jotka on muotoiltu tietyn mallin mukaan. Tämä malli määräytyy erityissääntöjen mukaisesti muodostetun merkkijonon avulla (muotomerkkijono).
Tämän perheen merkittävin jäsen on printf -funktio sekä monet muut funktiot, jotka on johdettu Cprintf -standardin kirjaston nimistä (joka on myös osa C++- ja Objective-C- standardikirjastoja ).
UNIX -käyttöjärjestelmäperheessä on myös printf -apuohjelma , joka palvelee samoja alustettuja tulosteita.
Fortranin FORMAT - operaattoria voidaan pitää tällaisen toiminnon varhaisena prototyyppinä . Merkkijonopohjainen päättelyfunktio ilmestyi C-kielen edeltäjiin ( BCPL ja B ). C- standardikirjaston määrittelyssä se sai tunnetuimman muodonsa (liput, leveys, tarkkuus ja koko). Tulostusmallin merkkijonosyntaksia (jota joskus kutsutaan muotomerkkijonoksi , muotomerkkijonoksi tai muotomerkkijonoksi ) käytettiin myöhemmin muissa ohjelmointikielissä (muunnelmilla näiden kielten ominaisuuksien mukaan). Pääsääntöisesti näiden kielten vastaavia toimintoja kutsutaan myös printf :ksi ja/tai sen johdannaisiksi.
Jotkut uudemmat ohjelmointiympäristöt (kuten .NET ) käyttävät myös muotomerkkijonolähtöisen tulosteen käsitettä, mutta eri syntaksilla.
Fortran Minulla oli jo operaattorit, jotka tarjosivat alustetun tulosteen. WRITE- ja PRINT -käskyjen syntaksi sisälsi etiketin, joka viittaa ei-suoritettavaan FORMAT -käskyyn , joka sisälsi muotomäärityksen. Määritykset olivat osa operaattorin syntaksia, ja kääntäjä pystyi välittömästi luomaan koodia, joka suorittaa suoraan tietojen muotoilun, mikä varmisti parhaan suorituskyvyn sen ajan tietokoneissa. Siinä oli kuitenkin seuraavat haitat:
Tulevan printf - toiminnon ensimmäinen prototyyppi ilmestyy BCPL - kielellä 1960 - luvulla . WRITEF -funktio ottaa muotomerkkijonon, joka määrittää tietotyypin erikseen itse tiedoista merkkijonomuuttujassa (tyyppi määritettiin ilman lippuja, leveys-, tarkkuus- ja kokokenttiä , mutta sitä edelsi jo prosenttimerkki ). [1] Muotomerkkijonon päätarkoitus oli välittää argumenttityyppejä (ohjelmointikielissä, joissa on staattinen kirjoitus funktiolle, jossa on ei-kiinteä muodollisten parametrien lista, hyväksytyn argumentin tyypin määrittäminen vaatii monimutkaista ja tehotonta mekanismia tyyppitietojen välittämiseksi yleisessä tapauksessa). WRITEF- funktio itsessään oli keino yksinkertaistaa tulostetta: funktiosarjan sijaan WRCH (tulostaa merkki), WRITES (tulostaa merkkijonon), WRITEN , WRITED , WRITEOCT , WRITEHEX (tulostaa numeroita eri muodoissa), yksi kutsu käytettiin, jossa oli mahdollista lomittaa "vain teksti" lähtöarvoihin. %
Sitä seurannut Bee- kieli vuonna 1969 käytti jo nimeä printf yksinkertaisen muotoisen merkkijonon kanssa (samanlainen kuin BCPL ), määritellen vain yhden kolmesta mahdollisesta tyypistä ja kahdesta numeromuodosta: desimaali ( ), oktaali ( ), merkkijonot ( ) ja merkit ( ), ja ainoa tapa muotoilla tulos näissä funktioissa oli lisätä merkkejä ennen ja jälkeen muuttujan arvon tulosteen. [2]%d%o%s%c
C-kielen ensimmäisen version ( 1970 ) käyttöönoton jälkeen printf -perheestä on tullut tärkein muototulostustyökalu. Kustannuksia muotomerkkijonon jäsentämisestä kunkin funktiokutsun yhteydessä pidettiin hyväksyttävinä, eikä kirjastoon otettu kullekin tyypille erikseen vaihtoehtoisia kutsuja. Toimintomäärittely sisältyi molempiin olemassa oleviin kielistandardeihin , jotka julkaistiin vuosina 1990 ja 1999 . Vuoden 1999 spesifikaatio sisältää joitain innovaatioita vuoden 1990 spesifikaatiosta.
C++-kieli käyttää standardia C-kirjastoa (vuoden 1990 standardin mukaan), mukaan lukien koko printf -perhe .
Vaihtoehtona C++-standardikirjasto tarjoaa joukon virransyöttö- ja tulosluokkia. Tämän kirjaston lähtökäskyt ovat tyyppiturvallisia eivätkä vaadi muotomerkkijonojen jäsentämistä joka kerta, kun niitä kutsutaan. Monet ohjelmoijat käyttävät kuitenkin edelleen printf -perhettä , koska niiden tulostussekvenssi on yleensä kompaktimpi ja käytetyn muodon olemus on selkeämpi.
Objective-C on melko "ohut" C:n lisäosa, ja siinä olevat ohjelmat voivat käyttää suoraan printf -perheen toimintoja .
C:n ja sen johdannaisten (C++, Objective-C) lisäksi monet muut ohjelmointikielet käyttävät printf-muotoista merkkijonosyntaksia:
Lisäksi useimpiin UNIX-tyyppisiin järjestelmiin sisältyvän printf - apuohjelman ansiosta printf : ää käytetään monissa shell-skripteissä ( sh , bash , csh , zsh jne.).
Jotkut uudemmat kielet ja ohjelmointiympäristöt käyttävät myös muotomerkkijonopohjaisen tulosteen käsitettä, mutta eri syntaksilla.
Esimerkiksi .Net Core Class Library (FCL) sisältää menetelmäperheen System.String.Format , System.Console.Write ja System.Console.WriteLine , joiden jotkin ylikuormitukset tulostavat tietonsa muotomerkkijonon mukaan. Koska täydelliset tiedot objektityypeistä ovat saatavilla .Net-ajoaikana, näitä tietoja ei tarvitse välittää muotomerkkijonossa.
Kaikkien funktioiden nimessä on kanta printf . Etuliitteet ennen funktion nimeä tarkoittavat:
Kaikki funktiot ottavat muotomerkkijonon yhdeksi parametriksi ( format ) ( merkkijonon syntaksin kuvaus alla). Palauttaa kirjoitettujen (tulostettujen) merkkien lukumäärän, ei sisällä tyhjää merkkiä merkin lopussa . Muotoillun tulosteen dataa sisältävien argumenttien määrän on oltava vähintään yhtä monta kuin muotomerkkijonossa on mainittu. "Lisäargumentit" jätetään huomiotta.
Perhefunktiot n ( snprintf , vsnprintf ) palauttavat niiden merkkien määrän, joka tulostettaisiin, jos parametri n (rajoittaa tulostettavien merkkien määrää) olisi riittävän suuri. Yksitavuisissa koodauksissa palautusarvo vastaa haluttua merkkijonon pituutta (ei sisällä tyhjää merkkiä lopussa).
S -perheen funktiot ( sprintf , snprintf , vsprintf , vsnprintf ) ottavat ensimmäiseksi parametrikseen osoittimen muistialueelle, johon tuloksena oleva merkkijono kirjoitetaan. Toiminnot, joilla ei ole rajoitusta kirjoitettavien merkkien määrälle, ovat vaarallisia toimintoja, koska ne voivat johtaa puskurin ylivuotovirheeseen , jos tulostemerkkijono on suurempi kuin tulostettavaksi varatun muistin koko.
F -perhefunktiot kirjoittavat merkkijonon mihin tahansa avoimeen virtaan ( virtaparametri ), erityisesti standardilähtövirtoihin ( stdout , stderr ). fprintf(stdout, format, …)vastaa printf(format, …).
V -perhefunktiot eivät ota argumentteja muuttuvana lukumääränä argumentteja (kuten kaikki muut printf-funktiot), vaan listana va list . Tässä tapauksessa, kun toimintoa kutsutaan, va end -makroa ei suoriteta.
W (ensimmäinen merkki ) -perhefunktiot ovat Microsoftin rajoitettu toteutus s -funktioperheestä : wsprintf , wnsprintf , wvsprintf , wvnsprintf . Nämä toiminnot on toteutettu dynaamisissa kirjastoissa user32.dll ja shlwapi.dll ( n funktiota). Ne eivät tue liukulukutulostusta, ja wnsprintf ja wvnsprintf tukevat vain vasemmalle tasattua tekstiä.
W -perheen toiminnot ( wprintf , swprintf ) toteuttavat tuen monitavuisille koodauksille, kaikki tämän perheen toiminnot toimivat osoittimilla monitavuisiin merkkijonoihin ( wchar_t ).
A -perheen funktiot ( asprintf , vasprintf ) varaavat muistia lähtömerkkijonolle malloc -funktiolla , muisti vapautetaan kutsuproseduurissa, jos funktion suorittamisessa tapahtuu virhe, muistia ei varata.
Paluuarvo: negatiivinen arvo — virhemerkki; onnistuessaan funktiot palauttavat kirjoitettujen/tulostettujen tavujen määrän (jättämättä tyhjän tavun lopussa), snprintf- funktio tulostaa tavumäärän, joka kirjoitettaisiin, jos n olisi tarpeeksi suuri.
Kutsuttaessa snprintf : ää n voi olla nolla (jolloin s voi olla nollaosoitin ), jolloin kirjoitusta ei tehdä, funktio palauttaa vain oikean palautusarvon.
C:ssä ja C++:ssa muotomerkkijono on nollapääteinen merkkijono. Kaikki merkit paitsi muotomäärittäjät kopioidaan tuloksena olevaan merkkijonoon muuttumattomina. Muotomääritteen alun vakiomerkki on merkki %( Percent sign ), itse merkin näyttämiseksi %käytetään sen tuplausta %%.
Muotomääritys näyttää tältä:
% [ liput ][ leveys ][ . tarkkuus [ koko ] tyyppiVaadittavat komponentit ovat muotomääritteen aloitusmerkki ( %) ja tyyppi .
LiputMerkki | Merkitse nimi | Merkitys | Tämän merkin puuttuessa | Merkintä |
---|---|---|---|---|
- | miinus | tuloste on tasattu vasemmalle kentän vähimmäisleveyden sisällä | oikealla | |
+ | Plussa | määritä aina merkki (plus tai miinus) näytettävälle desimaalilukuarvolle | vain negatiivisille luvuille | |
tilaa | laita välilyönti ennen tulosta, jos arvon ensimmäinen merkki ei ole merkki | Tulos voi alkaa numerolla. | + -merkki on etusijalla välilyöntiin nähden . Käytetään vain etumerkityille desimaaliarvoille. | |
# | ristikko | arvotuotannon "vaihtoehtoinen muoto". | Kun tulostetaan lukuja heksadesimaali- tai oktaalimuodossa, numeroa edeltää muotoominaisuus (0x tai 0, vastaavasti). | |
0 | nolla | täytä kenttä symbolilla Escape-sekvenssin leveyskentässä määritettyyn leveyteen0 | tyyny välilyönnillä | Käytetään tyypeille d , i , o , u , x , X , a , A , e , E , f , F , g , G . Tyypeille d , i , o , u , x , X , jos tarkkuus on määritetty, tämä lippu jätetään huomiotta. Muille tyypeille käyttäytyminen on määrittelemätön.
Jos miinus '-' -lippu on määritetty, sekin ohitetaan. |
Leveys (desimaali tai tähtimerkki ) määrittää kentän vähimmäisleveyden (mukaan lukien numeroiden etumerkki). Jos arvon esitys on suurempi kuin kentän leveys, merkintä on kentän ulkopuolella (esimerkiksi %2i arvolle 100 antaa kolmen merkin kentän arvon), jos arvon esitys on pienempi kuin määritetty luku, sitten se täytetään (oletusarvoisesti) välilyönneillä vasemmalla, käyttäytyminen voi vaihdella muiden asetettujen lippujen mukaan. Jos leveydeksi määritetään tähti, kentän leveys määritetään argumenttiluettelossa ennen tulosarvoa ( printf( "%0*x", 8, 15 );näyttää esimerkiksi tekstin 0000000f). Jos negatiivinen leveysmuuttaja on määritetty tällä tavalla, lippua - pidetään asetettuna ja leveysmuuttajan arvoksi asetetaan absoluuttinen.
TarkkuusmuuttajaTarkkuus määritetään pisteenä, jota seuraa desimaaliluku tai tähti ( * ). Jos numeroa tai tähteä ei ole (vain piste on olemassa), luvun oletetaan olevan nolla. Pistettä käytetään osoittamaan tarkkuutta, vaikka pilkku näytetään liukulukuja tulostettaessa.
Jos pisteen jälkeen on määritetty tähtimerkki, muotomerkkijonoa käsiteltäessä kentän arvo luetaan argumenttiluettelosta. (Samaan aikaan, jos tähtimerkki on sekä leveys- että tarkkuuskentässä, ensin ilmoitetaan leveys, sitten tarkkuus ja vasta sitten tulosteen arvo). Se printf( "%0*.*f", 8, 4, 2.5 );näyttää esimerkiksi tekstin 002.5000. Jos negatiivinen tarkkuusmuunnin määritetään tällä tavalla, tarkkuusmuuttajaa ei ole. [19]
KokomuuttajaKoko - kentässä voit määrittää funktiolle välitettävien tietojen koon. Tämän kentän tarve selittyy C-kielen funktiolle mielivaltaisen määrän parametrien välittämisen erikoisuuksilla: funktio ei voi "itsenäisesti" määrittää siirrettävän tiedon tyyppiä ja kokoa, joten tietoa parametrien tyypeistä ja niiden koosta. tarkka koko on annettava erikseen.
Kun otetaan huomioon kokomäärittelyjen vaikutus kokonaislukutietojen muotoiluun, on huomattava, että C- ja C++-kielissä on ketju etumerkittyjen ja etumerkittömien kokonaislukutyyppien pareja, jotka kokojen ei-laskevassa järjestyksessä ovat järjestetty seuraavasti:
allekirjoitettu tyyppi | Allekirjoittamaton tyyppi |
---|---|
allekirjoitettu char | allekirjoittamaton merkki |
allekirjoitettu lyhyt ( lyhyt ) | unsigned short int ( unsigned short ) |
allekirjoitettu int ( int ) | allekirjoittamaton int ( unsigned ) |
allekirjoitettu pitkä int ( pitkä ) | allekirjoittamaton pitkä int ( unsigned long ) |
pitkä pitkä int ( pitkä pitkä ) | allekirjoittamaton pitkä pitkä int ( allekirjoittamaton pitkä pitkä ) |
Tyyppien tarkkaa kokoa ei tunneta, lukuun ottamatta etumerkittyjä ja etumerkittömiä merkkityyppejä .
Parilliset allekirjoitetut ja allekirjoittamattomat tyypit ovat samankokoisia, ja molemmissa tyypeissä esitettävillä arvoilla on sama esitystapa.
Merkkityyppi on samankokoinen kuin etumerkillinen merkki ja etumerkitön merkki , ja se jakaa joukon edustavia arvoja yhden näistä tyypeistä. Lisäksi oletetaan, että char on toinen nimi jollekin näistä tyypeistä; tällainen oletus on hyväksyttävä tässä tarkastelussa.
Lisäksi C:llä on _Bool- tyyppi , kun taas C++:lla on bool -tyyppi .
Kun funktiolle välitetään argumentteja, jotka eivät vastaa funktion prototyypin muodollisia parametreja (jotka ovat kaikki lähtöarvoja sisältäviä argumentteja), nämä argumentit käyvät läpi standardin ylennyksiä , nimittäin:
Näin ollen printf-funktiot eivät voi ottaa argumentteja, joiden tyyppi on float , _Bool tai bool , tai kokonaislukutyyppejä, jotka ovat pienempiä kuin int tai unsigned .
Käytettyjen kokomääritteiden joukko riippuu tyyppimäärityksestä (katso alla).
määrittäjä | %d, %i, %o, %u, %x,%X | %n | Merkintä |
---|---|---|---|
puuttuu | int tai allekirjoittamaton int | osoitin int | |
l | long int tai unsigned long int | osoitin pitkä int | |
hh | Argumentti on tyyppiä int tai unsigned int , mutta se on pakotettu kirjoittamaan signed char tai unsigned char , vastaavasti | osoitin signed char | muodollisesti olemassa C-kielessä vuoden 1999 standardista lähtien ja C++:ssa 2011-standardista lähtien. |
h | Argumentti on tyyppiä int tai unsigned int , mutta se on pakotettu kirjoittamaan short int tai unsigned short int , vastaavasti | osoitin lyhyt int | |
ll | pitkä pitkä int tai allekirjoittamaton pitkä pitkä int | osoitin pitkä pitkä int | |
j | intmax_t tai uintmax_t | osoitin kohtaan intmax_t | |
z | size_t (tai kokoa vastaava signeerattu tyyppi) | osoitin etumerkittuun tyyppiin , joka vastaa kokoa koko_t | |
t | ptrdiff_t (tai vastaava allekirjoittamaton tyyppi) | osoitin kohtaan ptrdiff_t | |
L | __int64 tai allekirjoittamaton __int64 | osoitin kohtaan __int64 | Borland Builder 6:lle (määrittäjä llodottaa 32-bittistä numeroa) |
Tekniset tiedot hja hhniitä käytetään kompensoimaan vakiotyyppisiä ylennyksiä, kun siirrytään allekirjoitetuista tyypeistä allekirjoittamattomiin tai päinvastoin.
Tarkastellaan esimerkiksi C-toteutusta, jossa char -tyyppi on etumerkitty ja sen koko on 8 bittiä, int -tyypin koko on 32 bittiä ja käytetään lisätapaa negatiivisten kokonaislukujen koodaamiseen.
char c = 255 ; printf ( "%X" , c );Tällainen kutsu tuottaa tulosteen FFFFFFFF, joka ei ehkä ole sitä, mitä ohjelmoija odotti. Itse asiassa c :n arvo on (char)(-1) , ja tyypin edistämisen jälkeen se on -1 . Muotoa %Xkäytettäessä annettu arvo tulkitaan etumerkittömäksi eli 0xFFFFFFFF .
char c = 255 ; printf ( "%X" , ( signed char ) c ); char c = 255 ; printf ( "%hhX" , c );Näillä kahdella kutsulla on sama vaikutus ja ne tuottavat tulosteen FF. Ensimmäinen vaihtoehto mahdollistaa merkkien kertomisen välttämisen tyyppiä mainostettaessa, toinen kompensoi sen jo printf -funktion "sisällä" .
määrittäjä | %a, %A, %e, %E, %f, %F, %g,%G |
---|---|
puuttuu | kaksinkertainen |
L | pitkä tupla |
määrittäjä | %c | %s |
---|---|---|
puuttuu | Argumentti on tyyppiä int tai unsigned int , mutta se on pakotettu kirjoittamaan char | char* |
l | Argumentti on tyyppiä wint_t , mutta se on pakotettu kirjoittamaan wchar_t | wchar_t* |
Tyyppi ilmaisee arvon tyypin (C-ohjelmointikielen näkökulmasta katsottuna) lisäksi myös lähtöarvon erityistä esitystä (esim. numerot voidaan näyttää desimaali- tai heksadesimaalimuodossa). Kirjoitettu yhtenä hahmona. Toisin kuin muut kentät, se on pakollinen. Yhden koodinvaihtomerkkijonon suurin tuettu tulostekoko on standardien mukaan vähintään 4095 merkkiä. käytännössä useimmat kääntäjät tukevat huomattavasti suurempia tietomääriä.
Tyyppiarvot:
Nykyisestä maa -asennosta riippuen sekä pilkkua että pistettä (ja mahdollisesti toista symbolia) voidaan käyttää liukulukujen näyttämisessä. Printf : n käyttäytyminen suhteessa luvun murto- ja kokonaislukuosan erottavaan merkkiin määräytyy käytössä olevan kielen (tarkemmin sanottuna muuttujan LC NUMERIC ) mukaan. [kaksikymmentä]
Erikoismakrot laajennetun joukon kokonaislukutietotyyppien aliaksiaToinen C-standardi (1999) tarjoaa laajennetun joukon aliaksia kokonaislukutietotyypeille int N _t , uint N _t , int_least N _t , uint_least N _t , int_fast N _t , uint_fast N _t (missä N vaadittu bittisyvyys), intptr_t , uintptr_t , intmax_t , uintmax_t .
Jokainen näistä tyypeistä saattaa vastata mitä tahansa vakiomuotoista sisäänrakennettua kokonaislukutyyppiä. Muodollisesti ottaen kannettavaa koodia kirjoittaessaan ohjelmoija ei tiedä etukäteen, mitä standardia tai laajennettua kokomääritystä hänen tulisi soveltaa.
int64_t x = 100000000000 ; int leveys = 20 ; printf ( "%0*lli" , leveys , x ); Väärin, koska int64_t ei välttämättä ole sama kuin long long int .Jotta näiden tyyppien objektien tai lausekkeiden arvot voitaisiin päätellä kannettavalla ja kätevällä tavalla, toteutus määrittelee kullekin näistä tyypeistä joukon makroja, joiden arvot ovat merkkijonoja, jotka yhdistävät koko- ja tyyppimääritykset.
Makrojen nimet ovat seuraavat:
Pari allekirjoitettua ja allekirjoittamatonta tyyppiä | Makron nimi |
---|---|
int N_t ja uint N_t _ _ | PRITN |
int_least N _t ja uint_least N _t | PRITLEASTN |
int_fastN_t ja uint_fastN_t _ _ _ _ | PRITFASTN |
intmax_t ja uintmax_t | PRITMAX |
intptr_t ja uintptr_t | PRITPTR |
Tässä T on yksi seuraavista tyyppimäärityksistä: d, i, u, o, x, X.
int64_t x = 100000000000 ; int leveys = 20 ; printf ( "%0*" PRIi64 , leveys , x ); Oikea tapa tulostaa int64_t -tyyppinen arvo C-kielellä.Saatat huomata, että tyypeillä intmax_t ja uintmax_t on vakiokokomääritys j, joten makro on todennäköisesti aina määritelty muodossa . PRITMAX"jT"
Single UNIX -standardin (käytännöllisesti katsoen POSIX - standardia vastaavan ) mukaan seuraavat lisäykset printf:ään on määritelty suhteessa ISO C: hen XSI (X/Open System Interface) -laajennuksen alla:
GNU C -kirjasto ( libc ) lisää seuraavat laajennukset:
GNU libc tukee mukautetun tyypin rekisteröintiä, jolloin ohjelmoija voi määrittää tulosmuodon omille tietorakenteilleen. Rekisteröi uusi tyyppi funktiolla
int register_printf_function (int type, printf_function handler-function, printf_arginfo_function arginfo-function), jossa:
Uusien tyyppien määrittämisen lisäksi rekisteröinti mahdollistaa olemassa olevien tyyppien (kuten s , i ) uudelleenmäärittelyn.
Microsoft Visual CMicrosoft Visual Studio C/C++-ohjelmointikielille printf-määrityksen (ja muiden perhetoimintojen) muodossa tarjoaa seuraavat laajennukset:
kentän arvo | tyyppi |
---|---|
I32 | allekirjoitettu __int32 , allekirjoittamaton __int32 |
I64 | allekirjoitettu __int64 , allekirjoittamaton __int64 |
minä | ptrdiff_t , koko_t |
w | vastaa l merkkijonoille ja merkeille |
Maple matematiikkaympäristössä on myös printf-funktio, jossa on seuraavat ominaisuudet:
MuotoiluEsimerkki:
> printf("%a =%A", `+`, `+`); `+` = + > printf("%a =%m", `+`, `+`); `+` = I"+f*6"F$6#%(builtinGF$"$Q"F$F$F$F"%*suojattuG JohtopäätösMaplen fprintf-funktio ottaa joko tiedostokuvaajan (fopen palauttaa) tai tiedostonimen ensimmäisenä argumenttina. Jälkimmäisessä tapauksessa nimen tulee olla "symboli"-tyyppinen, jos tiedoston nimi sisältää pisteitä, niin se on suljettava backticksillä tai muunnettava muunnostoiminnolla (tiedoston_nimi, symboli).
Printf -perheen funktiot ottavat argumenttiluettelon ja niiden koon erillisenä parametrina (muotomerkkijonossa). Muotomerkkijonon ja hyväksyttyjen argumenttien välinen ristiriita voi johtaa arvaamattomaan toimintaan, pinon vioittumiseen, mielivaltaiseen koodin suorittamiseen ja dynaamisten muistialueiden tuhoutumiseen. Monia perheen toimintoja kutsutaan "unsafeiksi" ( englanniksi unsafe ), koska niillä ei ole edes teoreettista kykyä suojautua virheellisiltä tiedoilta.
Myöskään s -perheen funktioilla (ilman n :ää , kuten sprintf , vsprintf ) ei ole rajoituksia kirjoitetun merkkijonon enimmäiskoon suhteen ja ne voivat johtaa puskurin ylivuotovirheeseen (kun dataa kirjoitetaan varatun muistialueen ulkopuolelle).
Osana kutsumiskäytäntöä pinoncdecl puhdistaminen suoritetaan kutsutoiminnon avulla. Kun printf :ää kutsutaan , argumentit (tai osoittimet niihin) sijoitetaan siihen järjestykseen, jossa ne kirjoitetaan (vasemmalta oikealle). Kun muotomerkkijonoa käsitellään, printf -funktio lukee argumentit pinosta. Seuraavat tilanteet ovat mahdollisia:
C-kielen määritykset kuvaavat vain kaksi tilannetta (normaali toiminta ja lisäargumentit). Kaikki muut tilanteet ovat virheellisiä ja johtavat määrittelemättömään ohjelman toimintaan (todellisuudessa mikä johtaa mielivaltaisiin tuloksiin aina suunnittelemattomien koodiosien suorittamiseen).
Liian monta argumenttiaKun välitetään liikaa argumentteja, printf -funktio lukee argumentit, jotka tarvitaan muotoilevan merkkijonon käsittelemiseksi oikein ja palaa kutsuvaan funktioon. Kutsuva funktio tyhjentää spesifikaation mukaisesti pinon kutsutulle funktiolle välitetyistä parametreista. Tässä tapauksessa ylimääräisiä parametreja ei yksinkertaisesti käytetä, ja ohjelma jatkuu ilman muutoksia.
Ei tarpeeksi argumenttejaJos pinossa on vähemmän argumentteja kutsuttaessa printf : ää kuin tarvitaan muotomerkkijonon käsittelyyn, puuttuvat argumentit luetaan pinosta huolimatta siitä, että pinossa on mielivaltaisia tietoja (ei ole merkitystä printf :n työn kannalta ) . Jos tietojenkäsittely oli ”onnistui” (eli se ei lopettanut ohjelmaa, jumittunut tai kirjoittanut pinoon), kutsufunktioon palaamisen jälkeen pinoosoittimen arvo palautetaan alkuperäiseen arvoonsa, ja ohjelma jatkuu.
Kun käsitellään "ylimääräisiä" pinoarvoja, seuraavat tilanteet ovat mahdollisia:
Muodollisesti mikä tahansa ristiriita argumentin tyypin ja odotuksen välillä aiheuttaa ohjelman määrittelemättömän käyttäytymisen. Käytännössä on useita tapauksia, jotka ovat erityisen mielenkiintoisia ohjelmointikäytännön kannalta:
Muut tapaukset johtavat pääsääntöisesti ilmeisen virheelliseen käyttäytymiseen ja ovat helposti havaittavissa.
Kokonaisluvun tai liukulukuargumentin koko ei täsmääKokonaislukuargumentille (kokonaislukumuotomäärityksellä) seuraavat tilanteet ovat mahdollisia:
Todelliselle argumentille (todellisella muotomäärityksellä) lähtöarvo ei yleensä vastaa välitettyä arvoa, jos koko ei täsmää.
Yleensä jos jonkin argumentin koko on väärä, kaikkien myöhempien argumenttien oikea käsittely tulee mahdottomaksi, koska argumenttien osoittimeen tulee virhe. Tätä vaikutusta voidaan kuitenkin kompensoida kohdistamalla arvot pinossa.
Pinon arvojen tasaaminenMonilla alustoilla on kokonaislukujen ja/tai todellisten arvojen kohdistussäännöt, jotka edellyttävät (tai suosittelevat) niiden sijoittamista osoitteisiin, jotka ovat niiden koon kerrannaisina. Nämä säännöt koskevat myös funktion argumenttien välittämistä pinossa. Tässä tapauksessa useita odotettujen ja todellisten parametrien tyypeissä olevia epäsuhtauksia voi jäädä huomaamatta, mikä luo illuusion oikeasta ohjelmasta.
uint32_t a = 1 ; uint64_t b = 2 , c = 3 ; printf ( "%" PRId64 "%" PRId64 "%" PRId64 , b , a , c ); Tässä esimerkissä todellisella atyyppiparametrilla on uint32_tvirheellinen muotomäärittely, joka liittyy %"PRId64"tyyppiin uint64_t. Kuitenkin joissakin alustoissa, joissa on 32-bittinen tyyppi int, virhe voi jäädä huomaamatta hyväksytystä tavujärjestyksestä ja pinon kasvusuunnasta riippuen. Varsinaiset parametrit bja ckohdistetaan osoitteeseen, joka on niiden koon monikerta (kaksi kertaa suurempi kuin a). Ja arvojen "väliin" ajätetään btyhjä (yleensä nollattu) 32 bitin tila; kun tuoteluettelo käsitellään, %"PRId64"32-bittinen arvo atämän välilyönnin kanssa tulkitaan yhdeksi 64-bittiseksi arvoksi.Tällainen virhe voi ilmetä odottamatta siirrettäessä ohjelmakoodia toiselle alustalle, vaihdettaessa kääntäjää tai käännöstilaa.
Mahdollinen kokoeroC- ja C++-kielten määritelmät kuvaavat vain yleisimmät vaatimukset tietotyyppien koosta ja esityksestä. Siksi monilla alustoilla joidenkin muodollisesti erilaisten tietotyyppien esitystapa osoittautuu samaksi. Tämä aiheuttaa sen, että jotkut tyyppierot jäävät havaitsematta pitkään.
Esimerkiksi Win32-alustalla on yleisesti hyväksyttyä, että tyyppien intja koot long intovat samat (32 bittiä). Siten puhelu printf("%ld", 1)tai printf("%d", 1L)suoritetaan "oikein".
Tällainen virhe voi ilmetä odottamatta siirrettäessä ohjelmakoodia toiselle alustalle, vaihdettaessa kääntäjää tai käännöstilaa.
Kun kirjoitat ohjelmia C++-kielellä, tulee olla varovainen kokonaislukutyyppisten aliasten avulla ilmoitettujen muuttujien arvojen johtamisessa, erityisesti size_t, ja ptrdiff_t; C++-standardikirjaston muodollinen määritelmä viittaa ensimmäiseen C-standardiin (1990). Toinen C-standardi (1999) määrittelee kokomääritykset tyypeille size_tja useille muille tyypeille käytettäväksi samanlaisten objektien kanssa. ptrdiff_tMyös monet C++-toteutukset tukevat niitä.
koko_t s = 1 ; printf ( "%u" , s ); Tämä esimerkki sisältää virheen, joka voi ilmetä alustoissa, sizeof (unsigned int)joissa sizeof (size_t). koko_t s = 1 ; printf ( "%zu" , s ); Oikea tapa päätellä tyyppiobjektin arvo on size_tC-kielellä. Tyyppi ei täsmää, kun koko vastaaJos välitetyt argumentit ovat samankokoisia, mutta eri tyyppisiä, ohjelma toimii usein "melkein oikein" (ei aiheuta muistin käyttövirheitä), vaikka lähtöarvo on todennäköisesti merkityksetön. On huomattava, että parillisten kokonaislukutyyppien (etumerkit ja etumerkitön) sekoittaminen on sallittua, ei aiheuta määrittelemätöntä käyttäytymistä ja sitä käytetään joskus tarkoituksella käytännössä.
Kun käytetään muotomäärittelyä %s, argumenttiarvo, joka on muu kuin , kokonaisluku-, reaali- tai osoitintyyppiä char*, tulkitaan merkkijonon osoitteeksi. Tämä osoite voi yleisesti ottaen viitata mielivaltaisesti olemattomaan tai saavuttamattomaan muistialueeseen, mikä johtaa muistin käyttövirheeseen, tai muistialueeseen, joka ei sisällä viivaa, mikä johtaa järjettömään ulostuloon, mahdollisesti erittäin suureen. .
Koska printf (ja muut perheen funktiot) voivat tulostaa muotomerkkijonon tekstin ilman muutoksia, jos se ei sisällä escape-sekvenssejä, niin komennon tekstin ulostulo on mahdollista
printf(text_to_print);
Jos teksti_tulostettava on saatu ulkoisista lähteistä (lue tiedostosta , saatu käyttäjältä tai käyttöjärjestelmältä), prosenttimerkin läsnäolo tuloksena olevassa merkkijonossa voi johtaa erittäin ei-toivottuihin seurauksiin (ohjelman jäädyttämiseen asti).
Väärä koodiesimerkki:
printf(" Current status: 99% stored.");
Tämä esimerkki sisältää erotussekvenssin "% s" , joka sisältää koodinvaihtomerkin (%), lipun (välilyönnin) ja merkkijonotietotyypin ( s ). Vastaanotettuaan ohjaussekvenssin funktio yrittää lukea osoittimen merkkijonoon pinosta. Koska funktiolle ei ole välitetty lisäparametreja, pinosta luettava arvo on määrittelemätön. Tuloksena oleva arvo tulkitaan osoittimeksi nollapäätteiseen merkkijonoon. Tällaisen "merkkijonon" tulos voi johtaa mielivaltaiseen muistivedostilaan, muistin käyttövirheeseen ja pinon vioittumiseen. Tämän tyyppistä haavoittuvuutta kutsutaan muotomerkkijonohyökkäykseksi . [21]
Tulostettavien merkkien enimmäismäärä ei rajoita tulosta tulostavaa printf -toimintoa. Jos virheen tai laiminlyönnin seurauksena näyttöön tulee odotettua enemmän merkkejä, pahinta mitä voi tapahtua, on kuvan "tuhoaminen" näytöllä. Luotu analogisesti printf :n kanssa, sprintf - funktiota ei myöskään rajoitettu tuloksena olevan merkkijonon enimmäiskoon suhteen. Toisin kuin "infinite" -pääte, muisti, jonka sovellus varaa tuloksena olevalle merkkijonolle, on aina rajoitettu. Ja mikäli odotetut rajat ylitetään, tallennus tehdään muihin tietorakenteisiin kuuluville muistialueille (tai yleensä mahdottomille muistialueille, mikä tarkoittaa, että ohjelma kaatuu melkein kaikilla alustoilla). Kirjoittaminen mielivaltaisille muistialueille johtaa arvaamattomiin vaikutuksiin (jotka voivat ilmaantua paljon myöhemmin eikä ohjelmavirheen, vaan käyttäjän tietojen vioittumisen muodossa). Merkkijonon enimmäiskoon rajan puuttuminen on perustavanlaatuinen suunnitteluvirhe funktiota kehitettäessä. Tästä syystä sprintf- ja vsprintf- funktioiden tila on vaarallinen . Sen sijaan hän kehitti funktiot snprintf , vsnprintf , jotka ottavat lisäargumentin, joka rajoittaa tuloksena olevan merkkijonon enimmäismäärää. Swprintf- funktio, joka ilmestyi paljon myöhemmin (monitavuisten koodausten kanssa työskentelemiseen), ottaa tämän puutteen huomioon ja käyttää argumentin rajoittaakseen tuloksena olevaa merkkijonoa. (Siksi snwprintf- toimintoa ei ole ).
Esimerkki vaarallisesta kutsusta sprintf :lle:
charbuffer [65536]; char* nimi = hanki_käyttäjänimi_näppäimistöstä(); sprintf(puskuri, "Käyttäjänimi:%s", nimi);Yllä oleva koodi olettaa implisiittisesti, että käyttäjä ei kirjoita 65 tuhatta merkkiä näppäimistöllä ja puskurin "pitäisi riittää". Mutta käyttäjä voi ohjata syötteen toisesta ohjelmasta tai silti kirjoittaa yli 65 000 merkkiä. Tässä tapauksessa muistialueet vioittuvat ja ohjelman käyttäytyminen muuttuu arvaamattomaksi.
Printf - perheen toiminnot käyttävät C - tietotyyppiä . Näiden tyyppien koot ja niiden suhteet voivat vaihdella alustasta toiseen. Esimerkiksi 64-bittisillä alustoilla valitusta mallista ( LP64 , LLP64 tai ILP64 ) riippuen int- ja long -tyyppien koot voivat vaihdella. Jos ohjelmoija asettaa muotomerkkijonon "melkein oikeaksi", koodi toimii yhdellä alustalla ja antaa väärän tuloksen toisella (joissakin tapauksissa saattaa johtaa tietojen vioittumiseen).
Koodi printf( "text address: 0x%X", "text line" );toimii esimerkiksi oikein 32-bittisellä alustalla ( ptrdiff_t - koko ja int -koko 32 bittiä) ja 64-bittisessä IPL64-mallissa (jossa ptrdiff_t- ja int - koot ovat 64-bittisiä), mutta antaa virheellisen tuloksen 64-bittisellä alustalla. -bitin alusta LP64- tai LLP64-mallissa, jossa ptrdiff_t:n koko on 64 bittiä ja int :n koko on 32 bittiä. [22]
Oracle Javassa dynaamisella tunnisteella käärittyjä tyyppejäprintf käytetään funktion analogissa , [6] Embarcadero Delphissä - välikerros , [23] erilaisissa toteutuksissa C ++ : ssa [24] - toimintojen ylikuormitus , C + :ssa + 20 - muuttujapohjia. Lisäksi muodot ( jne .) eivät määritä argumentin tyyppiä, vaan vain lähtömuotoa, joten argumentin tyypin muuttaminen voi aiheuttaa hätätilanteen tai rikkoa korkean tason logiikan (esim. taulukon asettelu) - mutta älä pilaa muistia. array of const%d%f
Ongelmaa pahentaa muotomerkkijonojen riittämätön standardointi eri kääntäjissä: esimerkiksi Microsoftin kirjastojen varhaiset versiot eivät tukeneet "%lld"(sinun piti määrittää "%I64d"). Microsoftin ja GNU:n välillä on edelleen jako tyypin mukaan size_t: %Iuedellinen ja %zujälkimmäinen. GNU C ei vaadi swprintfmerkkijonon enimmäispituutta funktiossa (sinun täytyy kirjoittaa snwprintf).
Perhefunktiot printfovat käteviä ohjelmistojen lokalisoinnissa : esimerkiksi se on helpompi kääntää kuin «You hit %s instead of %s.»merkkijonopätkät «You hit »ja . Mutta tässäkin on ongelma: on mahdotonta järjestää korvattuja merkkijonoja paikkoihin, jotta saadaan: . « instead of »«.»«Вы попали не в <2>, а в <1>.»
Oracle Javassa ja Embarcadero Delphissaprintf käytetyt laajennukset mahdollistavat edelleen argumenttien järjestämisen uudelleen.
POSIX -standardin sisällä kuvataan printf -apuohjelma , joka muotoilee argumentit sopivan kaavan mukaan, kuten printf -funktio .
Apuohjelmalla on seuraava kutsumuoto: , missä printf format [argument …]
Unix-komennot | ||||||||
---|---|---|---|---|---|---|---|---|
|