Printf

Den aktuella versionen av sidan har ännu inte granskats av erfarna bidragsgivare och kan skilja sig väsentligt från versionen som granskades den 5 april 2015; kontroller kräver 72 redigeringar .

printf (från engelska  print formatted , "formatted printing") - ett generaliserat namn för en familj av funktioner eller metoder för standard eller välkända kommersiella bibliotek, eller inbyggda operatörer av vissa programmeringsspråk som används för formaterad utdata  - utdata till olika strömmar av värden av olika typer formaterade enligt en given mall. Denna mall bestäms av en sträng sammansatt enligt speciella regler (formatsträng).

Den mest anmärkningsvärda medlemmen i denna familj är printf- funktionen , såväl som ett antal andra funktioner som härrör från printfnamn i C -standardbiblioteket (som också är en del av C++- och Objective-C- standardbiblioteken ).

UNIX- familjen av operativsystem har också ett printf- verktyg som tjänar samma syften med formaterad utdata.

Fortrans FORMAT - operatör kan betraktas som en tidig prototyp av en sådan funktion . Den strängdrivna inferensfunktionen dök upp i föregångarna till C-språket ( BCPL och B ). I specifikationen för C- standardbiblioteket fick det sin mest välkända form (med flaggor, bredd, precision och storlek). Utdatamallens strängsyntax (ibland kallad formatsträngen , formatsträngen eller formatsträngen ) användes senare av andra programmeringsspråk (med variationer för att passa funktionerna i dessa språk). Som regel kallas motsvarande funktioner för dessa språk även printf och/eller derivator av det.

Vissa nyare programmeringsmiljöer (som .NET ) använder också konceptet med formatsträngsdriven utdata, men med en annan syntax.

Historik

Utseende

Fortran Jag hade redan operatörer som gav formaterad utdata. Syntaxen för WRITE- och PRINT -satserna inkluderade en etikett som hänvisade till en icke-körbar FORMAT -sats som innehöll en formatspecifikation. Specifierare var en del av syntaxen för operatören, och kompilatorn kunde omedelbart generera kod som direkt utför dataformatering, vilket säkerställde den bästa prestandan på dåtidens datorer. Det fanns dock följande nackdelar:

Den första prototypen av den framtida printf- funktionen dyker upp på BCPL- språket på 1960 -talet . WRITEF -funktionen tar en formatsträng som specificerar datatypen separat från själva data i strängvariabeln ( typen specificerades utan flaggor, bredd, precision och storleksfält, men föregicks redan av ett procenttecken %). [1] Huvudsyftet med formatsträngen var att skicka argumenttyper (i programmeringsspråk med statisk typning kräver att bestämma typen av det godkända argumentet för en funktion med en icke-fixerad lista med formella parametrar en komplex och ineffektiv mekanism för att skicka typinformation i det allmänna fallet). Själva WRITEF- funktionen var ett sätt att förenkla utdata: istället för en uppsättning funktioner WRCH (mata ut ett tecken), WRITES (mata ut en sträng), WRITEN , WRITED , WRITEOCT , WRITEHEX (utdatanummer i olika former), ett enda anrop användes där det var möjligt att interfoliera "bara text" med utdatavärden.

Bee- språket som följde det 1969 använde redan namnet printf med en enkel formatsträng (liknande BCPL ), och specificerade endast en av tre möjliga typer och två talrepresentationer: decimal ( ), oktal ( ), sträng ( ) och tecken ( ), och det enda sättet att formatera utdata i dessa funktioner var att lägga till tecken före och efter utmatningen av variabelns värde. [2]%d%o%s%c

C och derivator

Sedan introduktionen av den första versionen av C-språket ( 1970 ) har printf- familjen blivit det huvudsakliga formatverktyget. Kostnaden för att analysera formatsträngen med varje funktionsanrop ansågs acceptabel, och alternativa anrop för varje typ separat infördes inte i biblioteket. Funktionsspecifikationen ingick i båda befintliga språkstandarder , publicerade 1990 och 1999 . 1999 års specifikation innehåller några innovationer från 1990 års specifikation.

C++-språket använder standard C-biblioteket (enligt 1990 års standard), inklusive hela printf- familjen .

Som ett alternativ tillhandahåller standardbiblioteket C++ en uppsättning strömingångs- och utgångsklasser. Utdatasatserna för det här biblioteket är typsäkra och kräver inte tolkning av formatsträngar varje gång de anropas. Men många programmerare fortsätter att använda printf- familjen , eftersom utdatasekvensen med dem vanligtvis är mer kompakt och kärnan i formatet som används är tydligare.

Objective-C är ett ganska "tunt" tillägg till C, och program på det kan direkt använda funktionerna i printf- familjen .

Användning i andra programmeringsspråk

Förutom C och dess derivator (C++, Objective-C) använder många andra programmeringsspråk den printf-liknande formatsträngsyntaxen:

Dessutom, tack vare printf - verktyget som ingår i de flesta UNIX-liknande system, används printf i många skalskript (för sh , bash , csh , zsh , etc.).

Följare

Vissa nyare språk och programmeringsmiljöer använder också konceptet med formatsträngsdriven utdata, men med en annan syntax.

Till exempel har .Net Core Class Library (FCL) en familj av metoder System.String.Format , System.Console.Write och System.Console.WriteLine , några överbelastningar av vilka matar ut sina data enligt en formatsträng. Eftersom fullständig information om objekttyper är tillgänglig i .Net-runtime, finns det inget behov av att skicka denna information i formatsträngen.

Namn på familjefunktioner

Alla funktioner har stammen printf i sina namn . Prefixen före funktionsnamnet betyder:

Allmänna konventioner

Alla funktioner tar en formatsträng som en av parametrarna ( format ) (beskrivning av syntaxen för strängen nedan). Returnera antalet skrivna tecken (utskrivna), exklusive nolltecknet i slutet av . Antalet argument som innehåller data för formaterad utdata måste vara minst lika många som nämns i formatsträngen. "Extra" argument ignoreras.

De n familjefunktionerna ( snprintf , vsnprintf ) returnerar antalet tecken som skulle skrivas ut om parametern n (som begränsar antalet tecken som ska skrivas ut) var tillräckligt stor. I fallet med enkelbyte- kodningar motsvarar returvärdet den önskade längden på strängen (exklusive nolltecknet i slutet).

Funktionerna i s -familjen ( sprintf , snprintf , vsprintf , vsnprintf ) tar som sin första parameter( er ) en pekare till minnesområdet där den resulterande strängen kommer att skrivas. Funktioner som inte har en begränsning på antalet tecken som skrivs är osäkra funktioner, eftersom de kan leda till ett buffertspillfel om utdatasträngen är större än storleken på det minne som allokerats för utdata.

F- familjens funktioner skriver en sträng till vilken öppen ström som helst ( strömparametern ), i synnerhet till standardutgångsströmmarna ( stdout , stderr ). fprintf(stdout, format, …)motsvarande printf(format, …).

V- familjens funktioner tar argument inte som ett variabelt antal argument (som alla andra printf-funktioner), utan som en lista va-lista . I det här fallet, när funktionen anropas, exekveras inte va end -makrot .

Familjefunktionerna w (första tecken) är en begränsad Microsoft-implementering av s funktionsfamiljen : wsprintf , wnsprintf , wvsprintf , wvnsprintf . Dessa funktioner implementeras i de dynamiska biblioteken user32.dll och shlwapi.dll ( n funktioner). De stöder inte flyttalsutdata, och wnsprintf och wvnsprintf stöder endast vänsterjusterad text.

Funktionerna i w -familjen ( wprintf , swprintf ) implementerar stöd för multibyte-kodningar, alla funktioner i denna familj fungerar med pekare till multibyte-strängar ( wchar_t ).

Funktionerna för a -familjen ( asprintf , vasprintf ) allokerar minne för utgångssträngen med malloc- funktionen , minnet frigörs i anropsproceduren, i händelse av ett fel när funktionen körs, tilldelas inte minnet.

Beskrivning av funktioner

Parameternamn

Beskrivning av funktioner

Returvärde: negativt värde — feltecken; om de lyckas returnerar funktionerna antalet byte som skrivits/utmatats (ignorerar nullbyten i slutet), funktionen snprintf skriver ut antalet byte som skulle skrivas om n var tillräckligt stort.

När snprintf anropas kan n vara noll (i vilket fall s kan vara en nollpekare ), i vilket fall ingen skrivning görs, funktionen returnerar bara det korrekta returvärdet.

Formatera strängsyntax

I C och C++ är en formatsträng en nollterminerad sträng. Alla tecken utom formatspecifikationerna kopieras till den resulterande strängen oförändrade. Standardtecknet i början av formatspecifikationen är tecknet %( Procenttecken ), för att visa själva tecknet %används dess fördubbling %%.

Formatspecifikationens struktur

Formatspecifikationen ser ut så här:

% [ flaggor ][ bredd ][ . precision ][ storlek ] typ

De nödvändiga komponenterna är formatspecifikationen starttecken ( %) och typen .

Flaggor
Tecken Signera namn Menande I avsaknad av detta tecken Notera
- minus- utgångsvärdet är vänsterjusterat inom den minsta fältbredden till höger
+ ett plus ange alltid ett tecken (plus eller minus) för det visade decimala numeriska värdet endast för negativa tal
  Plats sätt ett mellanslag före resultatet om det första tecknet i värdet inte är ett tecken Utgången kan börja med ett nummer. Tecknet + har företräde framför blanksteg. Används endast för tecken med decimalvärden.
# gitter "alternativ form" av värdeproduktion När du matar ut tal i hexadecimalt eller oktalt format, kommer talet att föregås av en formatfunktion (0x respektive 0).
0 noll- fylla fältet till den bredd som anges i fältet Escape-sekvensbredd med symbolen0 pad med mellanslag Används för typerna d , i , o , u , x , X , a , A , e , E , f , F , g , G . För typerna d , i , o , u , x , X , om precision anges, ignoreras denna flagga. För andra typer är beteendet odefinierat.

Om en minus "-"-flagga anges ignoreras den flaggan också.

Breddmodifierare

Bredd (decimal eller asterisk ) anger den minsta fältbredden (inklusive tecknet för siffror). Om värderepresentationen är större än fältets bredd, är posten utanför fältet (till exempel %2i för ett värde på 100 ger ett fältvärde på tre tecken), om värderepresentationen är mindre än det angivna talet, då kommer det att vara vadderat (som standard) med mellanslag till vänster, beteendet kan variera beroende på andra flaggor. Om en asterisk anges som bredd, anges fältbredden i argumentlistan före utdatavärdet ( printf( "%0*x", 8, 15 );visar till exempel text 0000000f). Om en negativ breddmodifierare anges på detta sätt, anses flaggan - vara inställd och breddmodifierarvärdet sätts till absolut.

Noggrannhetsmodifierare
  • anger det minsta antalet tecken som ska visas vid bearbetning av typerna d , i , o , u , x , X ;
  • anger det minsta antalet tecken som måste visas efter decimaltecknet (komma) vid bearbetning av typerna a , A , e , E , f , F ;
  • det maximala antalet signifikanta tecken för typerna g och G ;
  • det maximala antalet tecken som ska skrivas ut för typ s ;

Precisionen anges som en period följt av ett decimaltal eller en asterisk ( * ), om det inte finns något tal eller en asterisk (endast en punkt finns) antas talet vara noll. En punkt används för att indikera precision även om ett kommatecken visas när flyttalsnummer matas ut.

Om ett asterisktecken anges efter punkten, läses värdet för fältet från listan med argument vid bearbetning av formatsträngen. (Samtidigt, om asterisken finns både i breddfältet och i precisionsfältet, indikeras först bredden, sedan precisionen och först därefter värdet för utdata). Till exempel kommer den printf( "%0*.*f", 8, 4, 2.5 );att visa texten 002.5000. Om en negativ precisionsmodifierare specificeras på detta sätt, så finns det ingen precisionsmodifierare. [19]

Storleksmodifierare

Storleksfältet låter dig ange storleken på data som skickas till funktionen . Behovet av detta fält förklaras av särdragen med att skicka ett godtyckligt antal parametrar till en funktion på C-språket: funktionen kan inte "oberoende" bestämma typen och storleken på de överförda data, så information om typen av parametrar och deras exakt storlek måste uttryckligen godkännas.

Med tanke på storleksspecifikationernas inflytande på formateringen av heltalsdata, bör det noteras att det i C- och C++-språken finns en kedja av par av signerade och osignerade heltalstyper, som, i icke-minskande storleksordning, är ordnat enligt följande:

signerad typ Osignerad typ
signerad röding osignerad röding
signerad kort ( kort ) unsigned short int ( unsigned short )
signerad int ( int ) osignerad int ( osignerad )
signerad long int ( long ) unsigned long int ( unsigned long )
signerad lång lång int ( lång lång ) unsigned long long int ( unsigned long long )

De exakta storlekarna på typerna är okända, med undantag för signerad röding och osignerad röding .

Parade signerade och osignerade typer har samma storlek, och värden som kan representeras i båda typerna har samma representation i dem.

Char -typen har samma storlek som de signerade och osignerade char- typerna och delar en uppsättning representativa värden med en av dessa typer. Det antas vidare att röding  är ett annat namn för en av dessa typer; Ett sådant antagande är acceptabelt för detta övervägande.

Dessutom har C typen _Bool medan C++ har typen bool .

När argument skickas till en funktion som inte motsvarar de formella parametrarna i funktionsprototypen (som alla är argument som innehåller utdatavärden), genomgår dessa argument standardkampanjer , nämligen:

  • flytargument kastas till dubbel ;
  • argument av typerna unsigned char , unsigned short , signed char och short casts till en av följande typer:
    • int om denna typ kan representera alla värden av den ursprungliga typen, eller
    • osignerad annars;
  • argument av typen _Bool eller bool casts till typen int .

Printf-funktioner kan alltså inte ta argument av typen float , _Bool , eller bool , eller heltalstyper som är mindre än int eller unsigned .

Uppsättningen storleksspecifikationer som används beror på typspecifikationen (se nedan).

specificator %d, %i, %o, %u, %x_%X %n Notera
saknas int eller osignerad int pekare till int
l long int eller osignerad long int pekare till lång int
hh Argumentet är av typen int eller unsigned int , men tvingas skriva signed char respektive unsigned char . pekare till signerad röding formellt existerat i C sedan 1999 års standard och i C++ sedan 2011 års standard.
h Argumentet är av typen int eller unsigned int , men tvingas skriva short int respektive unsigned short int . pekare till kort int
ll lång lång int eller osignerad lång lång int pekare till lång lång int
j intmax_t eller uintmax_t pekare till intmax_t
z size_t (eller storleksekvivalent signerad typ) pekare till en signerad typ som i storlek motsvarar size_t
t ptrdiff_t (eller en motsvarande osignerad typ) pekare till ptrdiff_t
L __int64 eller osignerad __int64 pekare till __int64 För Borland Builder 6 (specifikationen llförväntar sig ett 32-bitars nummer)

Specifikationer hoch hhanvänds för att kompensera för standardtypkampanjer i samband med övergångar från signerade till osignerade typer, eller vice versa.

Tänk till exempel på en C-implementering där char -typen är signerad och har en storlek på 8 bitar, int -typen har en storlek på 32 bitar och ett ytterligare sätt att koda negativa heltal används.

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

Ett sådant anrop kommer att producera utdata FFFFFFFF, vilket kanske inte är vad programmeraren förväntade sig. Faktum är att värdet på c är (char)(-1) , och efter typpromotion är det -1 . Användning av formatet %Xgör att det givna värdet tolkas som osignerat, det vill säga 0xFFFFFFFF .

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

Dessa två anrop har samma effekt och producerar utdata FF. Det första alternativet låter dig undvika teckenmultiplikationen när du marknadsför typen, det andra kompenserar för det redan "inuti" printf- funktionen .

specificator %a, %A, %e, %E, %f, %F, %g_%G
saknas dubbel
L lång dubbel
specificator %c %s
saknas Argumentet är av typen int eller unsigned int , men tvingas skriva char char*
l Argumentet är av typen wint_t , men tvingas skriva wchar_t wchar_t*
Typspecifikation

Typen indikerar inte bara typen av värdet (ur C-programmeringsspråkets synvinkel), utan också den specifika representationen av utmatningsvärdet (till exempel kan siffror visas i decimal eller hexadecimal form). Skrivet som ett enda tecken. Till skillnad från andra fält är det obligatoriskt. Den maximala utdatastorleken som stöds från en enda escape-sekvens är enligt standarder minst 4095 tecken; i praktiken stöder de flesta kompilatorer betydligt större mängder data.

Typvärden:

  • d , i  — signerat decimaltal, standardtyp är int . Som standard skrivs det med högerjustering, tecknet skrivs endast för negativa tal. Till skillnad från funktionerna i scanf- familjen , för funktionerna i printf- familjen, är %d- och %i- specifikationerna helt synonyma;
  • o  — osignerat oktalt nummer, standardtypen är osignerad int ;
  • u  är ett osignerat decimaltal, standardtypen är osignerad int ;
  • x och X  är hexadecimala tal utan tecken, x använder små bokstäver (abcdef), X använder stora bokstäver (ABCDEF), standardtypen är osignerad int ;
  • f och F  är flyttal, standardtypen är dubbel . Som standard matas de ut med en precision på 6, om modulotalet är mindre än ett skrivs en 0 före decimalkomma. Värden på ±∞ presenteras i formen [-]inf eller [-]oändlighet (beroende på plattformen); värdet på Nan representeras som [-]nan eller [-]nan(valfri text nedan) . Genom att använda F skrivs de angivna värdena ut med versaler ( [-]INF , [-]INFINITY , NAN ).
  • e och E  är flyttal i exponentiell notation (av formen 1.1e+44), standardtypen är dubbel . e matar ut tecknet "e" med gemener, E  - med versaler (3.14E+0);
  • g och G  är ett flyttal, standardtypen är dubbel . Representationsformen beror på kvantitetens värde ( f eller e ). Formatet skiljer sig något från flyttal genom att inledande nollor till höger om decimalkomma inte matas ut. Dessutom visas inte semikolondelen om talet är ett heltal;
  • a och A (med början från C-språkstandarderna från 1999 och C++ från 2011) — ett flyttal i hexadecimal form, standardtypen är dubbel ;
  • c  — utmatning av symbolen med koden som motsvarar det godkända argumentet, standardtypen är int ;
  • s  - utmatning av en sträng med en noll-avslutande byte; om längdmodifieraren är l , matas strängen wchar_t* ut . På Windows beror värdena för typ s på vilken typ av funktioner som används. Om en familj av printffunktioner används, så betecknar s strängen char* . Om en familj av wprintffunktioner används, betecknar s strängen wchar_t* .
  • S  är samma som s med längdmodifierare l ; På Windows beror värdet på typ S på vilken typ av funktioner som används. Om en familj av printffunktioner används står S för strängen wchar_t* . Om en familj av wprintffunktioner används, så betecknar S strängen char* .
  • p - pekarens  utdata kan utseendet variera avsevärt beroende på den interna representationen i kompilatorn och plattformen (till exempel använder 16-bitars MS-DOS-plattformen notationen av formen FFEC:1003, 32-bitarsplattformen med platt adressering använder adressen av formuläret 00FA0030);
  • n  - registrera för pekare, skickat som ett argument, antalet tecken skrivna vid tidpunkten för förekomsten av kommandosekvensen innehållande n ;
  • %  - tecken för att visa procenttecknet (%), som används för att aktivera utmatning av procenttecken i printf-strängen, som alltid används i formuläret %%.
Utdata av flyttalsnummer

Beroende på aktuell lokal kan både komma och punkt (och eventuellt ytterligare en symbol) användas för att visa flyttal. Beteendet för printf med avseende på tecknet som separerar bråk- och heltalsdelen av talet bestäms av den lokala platsen som används (mer exakt variabeln LC NUMERIC ). [tjugo]

Speciella makron för en utökad uppsättning heltalsdatatypalias

Second C Standard (1999) tillhandahåller en utökad uppsättning alias för heltalsdatatyper int N _t , uint N _t , int_least N _t , uint_least N _t , int_fast N _t , uint_fast N _t (där N är det nödvändiga bitdjupet), intptr_t , uintptr_t , intmax_t , uintmax_t .

Var och en av dessa typer kanske matchar någon av de standardinbyggda heltalstyperna. Formellt sett, när man skriver portabel kod, vet programmeraren inte i förväg vilken standard eller utökad storleksspecifikation han ska tillämpa.

int64_t x = 100000000000 ; int bredd = 20 ; printf ( "%0*lli" , bredd , x ); Fel, eftersom int64_t kanske inte är samma sak som long long int .

För att kunna härleda värdena för objekt eller uttryck av dessa typer på ett portabelt och bekvämt sätt, definierar implementeringen för var och en av dessa typer en uppsättning makron vars värden är strängar som kombinerar storlek och typspecifikationer.

Makronamn är följande:

Ett par signerade och osignerade typer Makronamn
int N_t och uint N_t _ _ PRITN
int_least N _t och uint_least N _t PRITLEASTN
int_fastN_t och uint_fastN_t _ _ _ _ PRITFASTN
intmax_t och uintmax_t PRITMAX
intptr_t och uintptr_t PRITPTR

Här T är en av följande typspecifikationer: d, i, u, o, x, X.

int64_t x = 100000000000 ; int bredd = 20 ; printf ( "%0*" PRIi64 , width , x ); Det korrekta sättet att mata ut ett värde av typen int64_t i C-språk.

Du kanske märker att typerna intmax_t och uintmax_t har en standardstorleksspecifikation j, så makrot är med största sannolikhet alltid definierat som . PRITMAX"jT"

XSI-tillägg i Single Unix Standard

Under Single UNIX- standarden (nästan likvärdig med POSIX- standarden ) definieras följande tillägg till printf i förhållande till ISO C, under XSI -tillägget (X/Open System Interface):

  • Möjligheten att mata ut en godtycklig parameter med nummer läggs till (indikeras som n$omedelbart efter tecknet i början av kontrollsekvensen, till exempel printf("%1$d:%2$.*3$d:%4$.*3$d\n", hour, min, precision, sec);).
  • Tillagd flagga "'" (enkelt citattecken), som för typerna d , i , o , u föreskriver att separera klasser med motsvarande tecken.
  • typ C ekvivalent med lc ISO C (teckenutgång av typen wint_t ).
  • typ S motsvarande ls ISO C (strängutgång som wchar_t* )
  • Lade till felkoder EILSEQ, EINVAL, ENOMEM, EOVERFLOW.

Icke-standardiserade tillägg

GNU C Library

GNU C-biblioteket ( libc ) lägger till följande tillägg:

  • typ m skriver ut värdet på den globala variabeln errno (felkoden för den sista funktionen).
  • typ C motsvarar lc .
  • flaggan ' (enkla citattecken) används för att separera klasser vid utskrift av nummer. Separationsformatet beror på LC_NUMERIC
  • storleken på q indikerar typen long long int (på system där long long int inte stöds är detta samma som long int
  • storlek Z är ett alias för z , introducerades i libc före tillkomsten av C99-standarden och rekommenderas inte för användning i ny kod.
Registrera dina egna typer

GNU libc stöder anpassad typregistrering, vilket gör att programmeraren kan definiera utdataformatet för sina egna datastrukturer. För att registrera en ny typ , använd funktionen
int register_printf_function (int type, printf_function handler-function, printf_arginfo_function arginfo-function), där:

  • typ  — bokstav för typen (om typ = 'Y' kommer anropet att se ut som '%Y');
  • handler-function  - en pekare till en funktion som anropas av printf-funktioner om typen som anges i typ påträffas i formatsträngen ;
  • arginfo-funktion  är en pekare till en funktion som kommer att anropas av funktionen parse_printf_format .

Förutom att definiera nya typer tillåter registreringen att befintliga typer (som s , i ) kan omdefinieras.

Microsoft Visual C

Microsoft Visual Studio för programmeringsspråken C/C++ i formatet printf-specifikationen (och andra familjefunktioner) tillhandahåller följande tillägg:

  • storlek låda:
fältvärde sorts
I32 signerad __int32 , osignerad __int32
I64 signerad __int64 , osignerad __int64
jag ptrdiff_t , storlek_t
w motsvarande l för strängar och tecken
lönn

Maple -mattemiljön har också en printf-funktion som har följande funktioner:

Formatering
    • %a, %A: Maple-objektet kommer att returneras i textnotation, detta fungerar för alla objekt (t.ex. matriser, funktioner, moduler, etc.). Den gemena bokstaven instruerar att omge tecken (namn) med backticks som ska omges av backticks i inmatningen till printf.
    • %q, %Q: samma som %a/%A, men inte bara ett argument kommer att behandlas, utan allt från det som matchar formateringsflaggan. Således kan %Q/%q-flaggan endast visas sist i formatsträngen.
    • %m: Formatera objektet enligt dess interna Maple-representation. Används praktiskt för att skriva variabler till en fil.

Exempel:

> printf("%a =%A", `+`, `+`); `+` = + > printf("%a =%m", `+`, `+`); `+` = I"+f*6"F$6#%(builtinGF$"$Q"F$F$F$F"%*protectedG Slutsats

Maples fprintf-funktion tar antingen en fildeskriptor (returneras av fopen) eller ett filnamn som sitt första argument. I det senare fallet måste namnet vara av typen "symbol", om filnamnet innehåller punkter, måste det omges av backticks eller konverteras med funktionen konvertera (filnamn, symbol).

Sårbarheter

Funktionerna i familjen printf tar en lista med argument och deras storlek som en separat parameter (i formatsträngen). En oöverensstämmelse mellan formatsträngen och de godkända argumenten kan leda till oförutsägbart beteende, korruption av stack, exekvering av godtycklig kod och förstörelse av dynamiska minnesområden. Många funktioner i familjen kallas "osäkra" ( engelska  unsafe ), eftersom de inte ens har den teoretiska förmågan att skydda sig mot felaktiga uppgifter.

Dessutom har funktioner i s -familjen (utan n , såsom sprintf , vsprintf ) inga gränser för den maximala storleken på den skrivna strängen och kan leda till ett buffertspillfel (när data skrivs utanför det allokerade minnesområdet).

Beteende när formatsträng och godkända argument inte matchar

Som en del av cdecl anropskonventionen görs stackrensning av anropsfunktionen. När printf anropas placeras argumenten (eller pekarna till dem) i den ordning som de är skrivna (vänster till höger). När formatsträngen bearbetas läser printf- funktionen argument från stacken. Följande situationer är möjliga:

  • antalet och typen av argument matchar de som anges i formatsträngen (normal funktionsoperation)
  • fler argument skickas till funktionen än vad som anges i formatsträngen (extra argument)
  • Färre argument skickas till funktionen än vad som krävs av formatsträngen (otillräckliga argument)
  • Fel storleksargument skickade till funktion
  • Argument av rätt storlek men fel typ skickades till funktionen

C-språkspecifikationerna beskriver endast två situationer (normal drift och extra argument). Alla andra situationer är felaktiga och leder till odefinierat programbeteende (i verkligheten leder det till godtyckliga resultat, upp till exekvering av oplanerade kodsektioner).

För många argument

När ett för stort antal argument skickas läser printf- funktionen de argument som krävs för att bearbeta formatsträngen korrekt och återgår till den anropande funktionen. Den anropande funktionen, i enlighet med specifikationen, rensar stacken från parametrarna som skickas till den anropade funktionen. I det här fallet används de extra parametrarna helt enkelt inte, och programmet fortsätter utan ändringar.

Inte tillräckligt med argument

Om det finns färre argument på stacken när man anropar printf än vad som krävs för att bearbeta formatsträngen, så läses de saknade argumenten från stacken, trots att det finns godtyckliga data på stacken (ej relevant för arbetet med printf ) . Om databearbetningen var "lyckad" (det vill säga den avslutade inte programmet, hängde sig eller skrev till stacken), efter att ha återgått till anropsfunktionen, returneras värdet på stackpekaren till sitt ursprungliga värde, och programmet fortsätter.

Vid bearbetning av "extra" stackvärden är följande situationer möjliga:

  • Lyckad läsning av en "extra" parameter för utdata (nummer, pekare, symbol, etc.) - det "nästan slumpmässiga" värdet som läses från stacken placeras i utdataresultatet. Detta utgör ingen fara för programmets funktion, men kan leda till att vissa data kompromitteras (utdata av stackvärden som en angripare kan använda för att analysera programmets funktion och få tillgång till programmets interna/privata information).
  • Ett fel när du läser ett värde från stacken (till exempel som ett resultat av att de tillgängliga stackvärdena uttömts eller tillgång till "icke-existerande" minnessidor) - ett sådant fel kommer troligen att orsaka att programmet kraschar.
  • Läser en pekare till en parameter. Strängar skickas med hjälp av en pekare, när man läser "godtycklig" information från stacken, används det lästa (nästan slumpmässiga) värdet som en pekare till ett slumpmässigt minnesområde. Programmets beteende i detta fall är odefinierat och beror på innehållet i detta minnesområde.
  • Att skriva en parameter med pekaren ( %n) - i det här fallet liknar beteendet situationen med läsning, men det kompliceras av möjliga biverkningar av att skriva till en godtycklig minnescell.
Argumenttyp matchar inte

Formellt orsakar varje diskrepans mellan typen av argument och förväntningarna odefinierat beteende hos programmet. I praktiken finns det flera fall som är särskilt intressanta ur programmeringspraktikens synvinkel:

  • Argumentet är av samma typ som förväntat, men av en annan storlek.
  • Argumentet har samma storlek som förväntat, men en annan typ.

Andra fall leder som regel till uppenbart felaktigt beteende, och upptäcks lätt.

Heltals- eller flyttalsargumentstorleken matchar inte

För ett heltalsargument (med en heltalsformatspecifikation) är följande situationer möjliga:

  • Passerar parametrar som är större än förväntat (läser de mindre från de större). I det här fallet, beroende på den accepterade byteordningen och riktningen för stacktillväxt, kan det visade värdet antingen sammanfalla med värdet på argumentet eller visa sig vara orelaterade till det.
  • Passerar parametrar som är mindre än förväntat (läser större från mindre). I det här fallet är en situation möjlig när stackområden som går utöver gränserna för de godkända argumenten läses. Funktionens beteende i detta fall liknar beteendet i en situation med brist på parametrar. Generellt sett överensstämmer inte utmatningsvärdet med det förväntade värdet.

För ett reellt argument (med en reell formatspecifikation), för varje storleksfelmatchning, matchar utdatavärdet som regel inte det godkända värdet.

Som regel, om storleken på ett argument är fel, blir korrekt bearbetning av alla efterföljande argument omöjlig, eftersom ett fel introduceras i pekaren till argumenten. Denna effekt kan dock kompenseras genom att justera värden på stacken.

Justera värden på stacken

Många plattformar har heltals- och/eller reella värdejusteringsregler som kräver (eller rekommenderar) att de placeras på adresser som är multiplar av deras storlek. Dessa regler gäller även för att skicka funktionsargument på stacken. I det här fallet kan ett antal felmatchningar i typen av förväntade och faktiska parametrar förbli obemärkt, vilket skapar en illusion av ett korrekt program.

uint32_t a = 1 ; uint64_t b = 2 , c = 3 ; printf ( "%" PRId64 "%" PRId64 "%" PRId64 , b , a , c ); I det här exemplet har den faktiska atypparametern uint32_ten ogiltig formatspecifikation kopplad %"PRId64"till typen uint64_t. Men på vissa plattformar med en 32-bitars typ int, beroende på den accepterade byteordningen och riktningen för stacktillväxt, kan felet förbli obemärkt. De faktiska parametrarna boch ckommer att justeras på en adress som är en multipel av deras storlek (dubbelt så stor som a). Och "mellan" värdena akommer bett tomt (vanligtvis nollställt) utrymme på 32 bitar att finnas kvar; när BOM bearbetas tolkas %"PRId64"32-bitarsvärdet atillsammans med detta blanksteg som ett enda 64-bitarsvärde.

Ett sådant fel kan oväntat uppstå när programkoden porteras till en annan plattform, ändras kompilatorn eller kompileringsläget.

Potentiell storleksskillnad

Definitionerna av C- och C++-språken beskriver endast de mest allmänna kraven för storlek och representation av datatyper. Därför, på många plattformar, visar sig representationen av vissa formellt olika datatyper vara densamma. Detta gör att vissa typer av felmatchningar inte upptäcks under lång tid.

Till exempel, på Win32-plattformen, är det allmänt accepterat att storlekarna på typerna intoch long intär desamma (32 bitar). Således kommer anropet printf("%ld", 1)eller printf("%d", 1L)att utföras "korrekt".

Ett sådant fel kan oväntat uppstå när programkoden porteras till en annan plattform, ändras kompilatorn eller kompileringsläget.

När man skriver program i C++-språket bör man vara försiktig med att härleda värdena för variabler som deklareras med hjälp av heltalstypalias, i synnerhet size_t, och ptrdiff_t; den formella definitionen av C++-standardbiblioteket hänvisar till den första C-standarden (1990). Second C Standard (1999) definierar storleksspecifikationer för typer size_toch och för ett antal andra typer för användning med liknande objekt. ptrdiff_tMånga C++-implementationer stöder dem också.

storlek_t s = 1 ; printf ( "%u" , s ); Det här exemplet innehåller en bugg som kan uppstå på plattformar sizeof (unsigned int)där sizeof (size_t). storlek_t s = 1 ; printf ( "%zu" , s ); Det korrekta sättet att härleda värdet av ett typobjekt är size_ti C-språk. Typen matchar inte när storleken matchas

Om argumenten som skickas är av samma storlek men har en annan typ, kommer programmet ofta att köras "nästan korrekt" (kommer inte att orsaka minnesåtkomstfel), även om utdatavärdet sannolikt är meningslöst. Det bör noteras att blandning av parade heltalstyper (signerade och osignerade) är tillåten, orsakar inte odefinierat beteende och används ibland medvetet i praktiken.

När du använder en formatspecifikation tolkas %sett argumentvärde av ett heltal, reellt eller annan pekartyp än char*, som adressen till en sträng. Denna adress, generellt sett, kan godtyckligt peka på ett obefintligt eller otillgängligt minnesområde, vilket kommer att leda till ett minnesåtkomstfel, eller till ett minnesområde som inte innehåller en linje, vilket kommer att leda till nonsensutmatning, möjligen mycket stor .

Formatsträngssårbarhet

Eftersom printf (och andra funktioner i familjen) kan mata ut texten i formatsträngen utan ändringar, om den inte innehåller escape-sekvenser, är textutmatningen av kommandot möjlig
printf(text_to_print);
Om text_to_print erhålls från externa källor (läs från en fil) , mottaget från användaren eller operativsystemet), kan närvaron av ett procenttecken i den resulterande strängen leda till extremt oönskade konsekvenser (upp till att programmet fryser).

Felaktig kodexempel:
printf(" Current status: 99% stored.");
Det här exemplet innehåller en escape-sekvens "% s" som innehåller escape-sekvenstecknet (%), en flagga (mellanslag) och en strängdatatyp ( s ). Funktionen, efter att ha tagit emot kontrollsekvensen, kommer att försöka läsa pekaren till strängen från stacken. Eftersom inga ytterligare parametrar skickades till funktionen är värdet som ska läsas från stacken odefinierat. Det resulterande värdet kommer att tolkas som en pekare till en nollterminerad sträng. Utmatningen av en sådan "sträng" kan leda till en godtycklig minnesdump, ett minnesåtkomstfel och en stackkorruption. Denna typ av sårbarhet kallas en formatsträngsattack .  [21]

Buffertspill

Printf -funktionen begränsas inte av det maximala antalet utdatatecken när ett resultat matas ut . Om, till följd av ett fel eller förbiseende, fler tecken än förväntat visas, är det värsta som kan hända att bilden förstörs på skärmen. Skapat i analogi med printf , var sprintf- funktionen inte heller begränsad i den maximala storleken på den resulterande strängen. Men till skillnad från den "oändliga" terminalen är minnet som applikationen allokerar för den resulterande strängen alltid begränsat. Och i händelse av att överskrida de förväntade gränserna, görs inspelningen i minnesområden som tillhör andra datastrukturer (eller i allmänhet i otillgängliga minnesområden, vilket innebär att programmet kraschar på nästan alla plattformar). Att skriva till godtyckliga minnesområden leder till oförutsägbara effekter (som kan dyka upp mycket senare och inte i form av ett programfel, utan i form av korruption av användardata). Avsaknaden av en gräns för den maximala strängstorleken är ett grundläggande planeringsfel vid utveckling av en funktion. Det är på grund av detta som funktionerna sprintf och vsprintf har den osäkra statusen . Istället utvecklade han funktionerna snprintf , vsnprintf , som tar ett ytterligare argument som begränsar den maximala resulterande strängen. Funktionen swprintf , som dök upp mycket senare (för att arbeta med multi-byte-kodningar), tar hänsyn till denna brist och tar ett argument för att begränsa den resulterande strängen. (Det är därför det inte finns någon snwprintf- funktion ).

Ett exempel på en farlig uppmaning till sprintf :

kolbuffert[65536]; char* name = get_user_name_from_keyboard(); sprintf(buffert, "Användarnamn:%s", namn);

Ovanstående kod förutsätter implicit att användaren inte kommer att skriva 65 tusen tecken på tangentbordet, och bufferten "borde räcka". Men användaren kan omdirigera indata från ett annat program, eller ändå ange mer än 65 000 tecken. I det här fallet kommer minnesområden att skadas och programmets beteende blir oförutsägbart.

Svårigheter att använda

Brist på typkontroll

Funktionerna i printf- familjen använder C -datatyper . Storleken på dessa typer och deras förhållande kan variera från plattform till plattform. Till exempel, på 64-bitars plattformar, beroende på den valda modellen ( LP64 , LLP64 eller ILP64 ), kan storlekarna på int och långa typer skilja sig åt. Om programmeraren ställer in formatsträngen till "nästan korrekt" kommer koden att fungera på en plattform och ge fel resultat på en annan (i vissa fall kan det leda till datakorruption).

Till exempel printf( "text address: 0x%X", "text line" );fungerar koden korrekt på en 32-bitars plattform ( ptrdiff_t storlek och int storlek 32 bitar) och på en 64-bitars IPL64 modell (där ptrdiff_t och int storlekar är 64 bitar), men kommer att ge ett felaktigt resultat på en 64 -bitars plattform av en LP64- eller LLP64-modell, där storleken på ptrdiff_t är 64 bitar och storleken på int är 32 bitar. [22]

I Oracle Java används lindade typer med dynamisk identifieringprintf i analogen av en funktion , [6] i Embarcadero Delphi  - ett mellanlager , [23] i olika implementeringar i C ++ [24]  - överbelastning av operationer , i C + + 20  - variabla mallar. Dessutom anger formaten ( , etc.) inte typen av argument, utan bara utdataformatet, så att ändra typen av argument kan orsaka en nödsituation eller bryta högnivålogik (till exempel "bryta" layout av tabellen) - men inte förstöra minnet. array of const%d%f

Brist på standardisering

Problemet förvärras av otillräcklig standardisering av formatsträngar i olika kompilatorer: till exempel stödde inte tidiga versioner av Microsoft-bibliotek "%lld"(du var tvungen att ange "%I64d"). Det finns fortfarande en uppdelning mellan Microsoft och GNU efter typ size_t: %Iuden förra och %zuden senare. GNU C kräver ingen swprintfmaximal stränglängd i en funktion (du måste skriva snwprintf).

Oförmåga att ordna om argument

Familjefunktionerna printfär bekväma för programvarulokalisering : det är till exempel lättare att översätta «You hit %s instead of %s.»än strängsnuttar «You hit »och . Men även här finns det ett problem: det är omöjligt att ordna om de ersatta strängarna på platser för att få: . « instead of »«.»«Вы попали не в <2>, а в <1>.»

Tilläggen printfsom används i Oracle Java och Embarcadero Delphi låter dig fortfarande ordna om argumenten.

printf verktyg

Inom POSIX- standarden beskrivs verktyget printf , som formaterar argument enligt lämpligt mönster, liknande printf- funktionen .

Verktyget har följande anropsformat: , where printf format [argument …]

  • format  är en formatsträng som i syntax liknar printf -funktionen formatsträng .
  • argument  är en lista med argument (0 eller fler) skrivna i strängform.

Implementeringsexempel

Exempel 1 C (programmeringsspråk)

#include <stdio.h> #include <locale.h> #define PI 3.141593 int main () { setlocale ( LC_ALL , "RUS" ); int nummer = 7 ; flytpajer = 12,75 ; _ int kostnad = 7800 ; printf ( "%d tävlande åt %f körsbärspajer. \n " , antal , pajer ); printf ( "Värdet på pi är %f \n " , PI ); printf ( "Adjö! Din konst kostar för mycket (%c%d) \n " , '$' , 2 * kostnad ); returnera 0 ; }

Exempel 2 C (programmeringsspråk)

#include <stdio.h> #define SIDOR 959 int main () { printf ( "*%d* \n " , PAGES ); printf ( "*%2d* \n " , PAGES ); printf ( "*%10d* \n " , PAGES ); printf ( "*%-10d* \n " , PAGES ); returnera 0 ; } /* Resultat: *959* *959* * 959* *959 * */

Exempel 3 C (programmeringsspråk)

#include <stdio.h> #define BLURB "Autentisk imitation!" int main () { const dubbel HYRA = 3852,99 ; printf ( "*%8f* \n " , HYRA ); printf ( "*%e* \n " , HYRA ); printf ( "*%4.2f* \n " , HYRA ); printf ( "*%3.1f* \n " , HYRA ); printf ( "*%10.3f* \n " , HYRA ); printf ( "*%10.3E* \n " , HYRA ); printf ( "*%+4.2f* \n " , HYRA ); printf ( "%x %X %#x \n " , 31 , 31 , 31 ); printf ( "**%d**% d% d ** \n " , 42 , 42 , -42 ); printf ( "**%5d**%5.3d**%05d**%05.3d** \n " , 6 , 6 , 6 , 6 ); printf ( " \n " ); printf ( "[%2s] \n " , BLURB ); printf ( "[%24s] \n " , BLURB ); printf ( "[%24.5s] \n " , BLURB ); printf ( "[%-24.5s] \n " , BLURB ); returnera 0 ; } /* resultat *3852,990000* *3,852990e+03* *3852,99* *3853,0* * 3852,990* * 3,853E+03* *+3852,99* 1f 1F 0x1f **0-42** 0x1f **-42** 0 **00006** 006** [Autentisk imitation!] [Autentisk imitation!] [Authe] [Authe ] */

Länkar

  1. Kort beskrivning av BCPL-språket . Hämtad 16 december 2006. Arkiverad från originalet 9 december 2006.
  2. B Språkguide Arkiverad 6 juli 2006.
  3. Beskrivning av sprintf- funktionen i Perl-dokumentationen . Hämtad 12 januari 2007. Arkiverad från originalet 14 januari 2007.
  4. En beskrivning av formateringsoperatorn för strängtyper i Python Arkiverad 9 november 2006.
  5. Beskrivning av PHPs printf -funktion . Hämtad 23 oktober 2006. Arkiverad från originalet 6 november 2006.
  6. 1 2 Beskrivning av funktionen java.io.PrintStream.printf() i Java 1.5 . Hämtad 12 januari 2007. Arkiverad från originalet 13 januari 2007.
  7. Beskrivning av printf- funktionen i Ruby-dokumentationen . Hämtad 3 december 2006. Arkiverad från originalet 5 december 2006.
  8. Beskrivning av string.format- funktionen i Lua-dokumentationen . Datum för åtkomst: 14 januari 2010. Arkiverad från originalet den 15 november 2013.
  9. Beskrivning av formatfunktionen i TCL-dokumentationen . Hämtad 14 april 2008. Arkiverad från originalet 4 juli 2007.
  10. Beskrivning av strängmönstret för printf i GNU Octave-dokumentationen . Hämtad 3 december 2006. Arkiverad från originalet 27 oktober 2006.
  11. Beskrivning av printf i Maple-dokumentationen{{subst:AI}}
  12. R. Fourer, D. M. Gay och B. W. Kernighan. AMPL: A Modeling Language for Mathematical Programming, 2nd Ed. Pacific Grove, CA: Brooks/Cole--Thomson Learning, 2003.
  13. GNU Emacs Lisp Reference Manual, Formatting Strings Arkiverad 27 september 2007 på Wayback Machine
  14. Beskrivning av Printf -modulen i OCaml-dokumentationen . Hämtad 12 januari 2007. Arkiverad från originalet 13 januari 2007.
  15. Beskrivning av Printf -modulen i Haskell-dokumentationen . Hämtad 23 juni 2015. Arkiverad från originalet 23 juni 2015.
  16. std::println! - Rost . doc.rust-lang.org. Hämtad 24 juli 2016. Arkiverad från originalet 18 augusti 2016.
  17. format . www.freepascal.org. Hämtad 7 december 2016. Arkiverad från originalet 24 november 2016.
  18. fmt - Go-programmeringsspråket . golang.org. Hämtad 25 mars 2020. Arkiverad från originalet 4 april 2020.
  19. §7.19.6.1 ISO/IEC 9899:TC2
  20. § 7.11.1.1 ISO/IEC 9899:TC2, LC_NUMERIC definierar i synnerhet representationsformen för decimalavgränsaren.
  21. Printf Vulnerability Description, Robert C. Seacord: Säker kodning i C och C++. Addison Wesley, september 2005. ISBN 0-321-33572-4
  22. Beskrivning av problemen med att porta applikationer från 32 till 64 bitars arkitektur . Hämtad 14 december 2006. Arkiverad från originalet 8 mars 2007.
  23. System.SysUtils.Format Arkiverad 11 januari 2013 på Wayback Machine 
  24. Till exempel boost::formatdokumentation Arkiverad 26 mars 2013 på Wayback Machine 

Källor

  • printf , fprintf , snprintf , vfprintf , vprintf , vsnprintf , vsprintf i ISO/IEC 9899:TC2 (ISO C) [3]
  • printf , fprintf , sprintf , snprintf i Single Unix -standarden [4]
  • vprintf , vfprintf , vsprintf , vsnprintf i POSIX -standarden [5]
  • wprintf , swprintf , wprintf i POSIX -standarden [6]
  • vfwprintf , vswprintf , vwprintf i POSIX -standarden [7]
  • wsprintf på MSDN [8]
  • wvnsprintf på MSDN [9]
  • wnsprintf på MSDN [10]
  • wvsprintf på MSDN [11]
  • wnsprintf på MSDN [12]
  • asprintf , vasprintf i man -sidor på Linux [13] , i libc- dokumentationen [14]
  • Se libc- manualen [15] för en beskrivning av formatsträngsyntaxen .
  • Beskrivning av formatsträngen i dokumentationen för Microsoft Visual Studio 2005 [16]
  • Beskrivning av register_printf_function [17] , [18]
  • Programmeringsspråk C. Föreläsningar och övningar. Författare: Stephen Prata. ISBN 978-5-8459-1950-2 , 978-0-321-92842-9; 2015

Se även