Printf

Den aktuelle version af siden er endnu ikke blevet gennemgået af erfarne bidragydere og kan afvige væsentligt fra den version , der blev gennemgået den 5. april 2015; kontroller kræver 72 redigeringer .

printf (fra engelsk  print formatted , "formatted printing") - et generaliseret navn for en familie af funktioner eller metoder i standard eller velkendte kommercielle biblioteker eller indbyggede operatører af nogle programmeringssprog, der bruges til formateret output  - output til forskellige strømme af værdier af forskellige typer formateret i henhold til en given skabelon. Denne skabelon er bestemt af en streng, der er sammensat efter særlige regler (formatstreng).

Det mest bemærkelsesværdige medlem af denne familie er printf -funktionen samt en række andre funktioner afledt af printfnavne i C -standardbiblioteket (som også er en del af C++- og Objective-C- standardbibliotekerne ).

UNIX -familien af ​​operativsystemer har også et printf -værktøj , der tjener de samme formål som formateret output.

Fortrans FORMAT - operatør kan betragtes som en tidlig prototype af en sådan funktion . Den strengdrevne inferensfunktion dukkede op i forløberne for C-sproget ( BCPL og B ). I specifikationen af ​​C- standardbiblioteket fik det sin mest velkendte form (med flag, bredde, præcision og størrelse). Outputskabelonens strengsyntaks (nogle gange kaldet formatstrengen , formatstrengen eller formatstrengen ) blev senere brugt af andre programmeringssprog (med variationer, der passer til funktionerne i disse sprog). Som regel kaldes de tilsvarende funktioner i disse sprog også printf og/eller afledte af det.

Nogle nyere programmeringsmiljøer (såsom .NET ) bruger også konceptet med formatstrengdrevet output, men med en anden syntaks.

Historie

Udseende

Fortran Jeg havde allerede operatører, der leverede formateret output. Syntaksen for WRITE- og PRINT -sætningerne indeholdt en etiket, der refererede til en ikke-eksekverbar FORMAT -sætning , der indeholdt en formatspecifikation. Specifikatorer var en del af operatørens syntaks, og compileren kunne straks generere kode, der direkte udfører dataformatering, hvilket sikrede den bedste ydeevne på datidens computere. Der var dog følgende ulemper:

Den første prototype af den fremtidige printf -funktion dukker op på BCPL -sproget i 1960'erne . WRITEF - funktionen tager en formatstreng, der specificerer datatypen separat fra selve dataene i strengvariablen (typen blev angivet uden felterne flag, bredde, præcision og størrelse, men blev allerede indledt af et procenttegn %). [1] Hovedformålet med formatstrengen var at videregive argumenttyper (i programmeringssprog med statisk skrivning kræver det en kompleks og ineffektiv mekanisme at bestemme typen af ​​det beståede argument for en funktion med en ikke-fast liste af formelle parametre for videregivelse af typeoplysninger i det generelle tilfælde). Selve WRITEF- funktionen var et middel til at forenkle outputtet: i stedet for et sæt funktioner WRCH (output et tegn), WRITES (output en streng), WRITEN , WRITED , WRITEOCT , WRITEHEX (outputtal i forskellige former), et enkelt opkald blev brugt, hvor det var muligt at sammenflette "bare tekst" med outputværdier.

Bi -sproget , der fulgte det i 1969 , brugte allerede navnet printf med en simpel formatstreng (svarende til BCPL ), der kun specificerede en af ​​tre mulige typer og to talrepræsentationer: decimal ( ), oktal ( ), strenge ( ) og tegn ( ), og den eneste måde at formatere outputtet i disse funktioner var at tilføje tegn før og efter outputtet af variablens værdi. [2]%d%o%s%c

C og derivater

Siden introduktionen af ​​den første version af C-sproget ( 1970 ) er printf -familien blevet det primære format-outputværktøj. Omkostningerne ved at parse formatstrengen med hvert funktionskald blev anset for acceptable, og alternative kald for hver type separat blev ikke introduceret i biblioteket. Funktionsspecifikationen var inkluderet i begge eksisterende sprogstandarder , offentliggjort i 1990 og 1999 . 1999-specifikationen indeholder nogle innovationer fra 1990-specifikationen.

C++-sproget bruger standard C-biblioteket (ifølge 1990-standarden), inklusive hele printf- familien .

Som et alternativ giver C++ standardbiblioteket et sæt strøminput- og outputklasser. Output-sætningerne i dette bibliotek er typesikre og kræver ikke parsing af formatstrenge, hver gang de kaldes. Imidlertid fortsætter mange programmører med at bruge printf -familien , da outputsekvensen med dem normalt er mere kompakt, og essensen af ​​det anvendte format er klarere.

Objective-C er en ret "tynd" tilføjelse til C, og programmer på den kan direkte bruge funktionerne i printf -familien .

Brug på andre programmeringssprog

Ud over C og dets derivater (C++, Objective-C) bruger mange andre programmeringssprog den printf-lignende formatstrengsyntaks:

Derudover, takket være printf -værktøjet, der er inkluderet i de fleste UNIX-lignende systemer, bruges printf i mange shell-scripts (til sh , bash , csh , zsh , osv.).

Følgere

Nogle nyere sprog og programmeringsmiljøer bruger også konceptet med formatstrengdrevet output, men med en anden syntaks.

For eksempel har .Net Core Class Library (FCL) en familie af metoder System.String.Format , System.Console.Write og System.Console.WriteLine , hvoraf nogle overbelastninger udsender deres data i henhold til en formatstreng. Da fuldstændig information om objekttyper er tilgængelig i .Net runtime, er det ikke nødvendigt at videregive disse oplysninger i formatstrengen.

Navngivning af familiefunktioner

Alle funktioner har stammen printf i deres navn . Præfikserne før funktionsnavnet betyder:

Generelle konventioner

Alle funktioner tager en formatstreng som en af ​​parametrene ( format ) (beskrivelse af strengens syntaks nedenfor). Returner antallet af skrevne tegn (udskrevet), ikke inklusive null-tegnet i slutningen af ​​. Antallet af argumenter, der indeholder data til formateret output, skal være mindst lige så mange som nævnt i formatstrengen. "Ekstra" argumenter ignoreres.

De n familiefunktioner ( snprintf , vsnprintf ) returnerer antallet af tegn, der ville blive udskrevet, hvis parameteren n (begrænser antallet af tegn, der skal udskrives) var stor nok. I tilfælde af enkelt-byte- kodninger svarer returværdien til den ønskede længde af strengen (ikke inklusive null-tegnet i slutningen).

Funktionerne i s -familien ( sprintf , snprintf , vsprintf , vsnprintf ) tager som deres første parameter ( er ) en pointer til hukommelsesområdet, hvor den resulterende streng vil blive skrevet. Funktioner, der ikke har en grænse for antallet af skrevne tegn, er usikre funktioner, da de kan føre til en bufferoverløbsfejl , hvis outputstrengen er større end størrelsen af ​​den hukommelse, der er allokeret til output.

F - familiefunktionerne skriver en streng til enhver åben strøm ( stream- parameteren ), især til standardoutputstrømmene ( stdout , stderr ). fprintf(stdout, format, …)svarende til printf(format, …).

V- familiefunktionerne tager argumenter ikke som et variabelt antal argumenter (som alle andre printf-funktioner), men som en liste va-liste . I dette tilfælde, når funktionen kaldes, udføres va end- makroen ikke.

Familiefunktionerne w (første tegn) er en begrænset Microsoft-implementering af s -familien af ​​funktioner : wsprintf , wnsprintf , wvsprintf , wvnsprintf . Disse funktioner er implementeret i de dynamiske biblioteker user32.dll og shlwapi.dll ( n funktioner). De understøtter ikke output med flydende komma, og wnsprintf og wvnsprintf understøtter kun venstrejusteret tekst.

Funktionerne i w -familien ( wprintf , swprintf ) implementerer understøttelse af multibyte-kodninger, alle funktioner i denne familie arbejder med pointere til multibyte-strenge ( wchar_t ).

Funktionerne i en familie ( asprintf , vasprintf ) allokerer hukommelse til outputstrengen ved hjælp af malloc -funktionen , hukommelsen frigøres i opkaldsproceduren, i tilfælde af en fejl under udførelse af funktionen, allokeres hukommelsen ikke.

Beskrivelse af funktioner

Parameternavne

Beskrivelse af funktioner

Returværdi: negativ værdi — fejltegn; hvis det lykkes, returnerer funktionerne antallet af bytes skrevet/output (ignorerer null-byten i slutningen), snprintf- funktionen udskriver antallet af bytes, der ville blive skrevet, hvis n var stor nok.

Når man kalder snprintf , kan n være nul (i hvilket tilfælde s kan være en null pointer ), i hvilket tilfælde der ikke skrives, funktionen returnerer kun den korrekte returværdi.

Formater strengsyntaks

I C og C++ er en formatstreng en null-termineret streng. Alle tegn undtagen formatangivelser kopieres til den resulterende streng uændret. Standardtegnet for begyndelsen af ​​formatspecifikationen er tegnet %( Procenttegn ), for at vise selve tegnet %bruges dets fordobling %%.

Formatspecifikationens struktur

Formatspecifikationen ser sådan ud:

% [ flag ][ width ][ . præcision ][ størrelse ] type

De nødvendige komponenter er formatspecifikationens starttegn ( %) og typen .

Flag
Skilt Tegn navn Betyder I mangel af dette tegn Bemærk
- minus outputværdien er venstrejusteret inden for den mindste feltbredde til højre
+ et plus angiv altid et fortegn (plus eller minus) for den viste decimaltalværdi kun for negative tal
  plads sæt et mellemrum før resultatet, hvis det første tegn i værdien ikke er et tegn Udgangen kan starte med et tal. Tegnet + har forrang over mellemrumstegnet. Bruges kun til decimalværdier med fortegn.
# gitter "alternativ form" for værdioutput Når du udskriver tal i hexadecimalt eller oktalt format, vil tallet blive indledt af en formatfunktion (henholdsvis 0x eller 0).
0 nul udfyld feltet til den bredde, der er angivet i feltet Escape -sekvensbredde med symbolet0 pude med mellemrum Bruges til typer d , i , o , u , x , X , a , A , e , E , f , F , g , G. For typer d , i , o , u , x , X , hvis præcision er angivet, ignoreres dette flag. For andre typer er adfærden udefineret.

Hvis et minus '-' flag er angivet, ignoreres dette flag også.

Breddemodifikator

Bredde (decimal eller stjernetegn ) angiver den mindste feltbredde (inklusive tegnet for tal). Hvis værdigengivelsen er større end feltets bredde, er indtastningen uden for feltet (f.eks. vil %2i for en værdi på 100 give en feltværdi på tre tegn), hvis værdigengivelsen er mindre end det angivne tal, så vil det være polstret (som standard) med mellemrum til venstre, adfærden kan variere afhængigt af andre flag sat. Hvis en stjerne er angivet som bredden, angives feltbredden i argumentlisten før outputværdien ( printf( "%0*x", 8, 15 );vil f.eks. vise tekst 0000000f). Hvis en negativ breddemodifikator er angivet på denne måde, betragtes flaget - som sat, og breddemodifikatorværdien er sat til absolut.

Nøjagtighedsmodifikator
  • angiver det mindste antal tegn, der skal vises ved behandling af typer d , i , o , u , x , X ;
  • angiver det mindste antal tegn, der skal stå efter decimaltegnet (kommaet) ved behandling af typer a , A , e , E , f , F ;
  • det maksimale antal signifikante tegn for typerne g og G ;
  • det maksimale antal tegn, der skal udskrives for type s ;

Præcisionen er angivet som et punktum efterfulgt af et decimaltal eller en stjerne ( * ), hvis der ikke er et tal eller en stjerne (kun en prik er til stede), antages tallet at være nul. En prik bruges til at angive præcision, selvom der vises et komma, når der udlæses flydende kommatal.

Hvis der er angivet et stjernetegn efter prikken, læses værdien for feltet fra listen over argumenter, når formatstrengen behandles. (Samtidig, hvis stjernetegnet er både i breddefeltet og i præcisionsfeltet, angives først bredden, derefter præcisionen og først derefter værdien for output). For eksempel printf( "%0*.*f", 8, 4, 2.5 );vil den vise teksten 002.5000. Hvis en negativ præcisionsmodifikator er angivet på denne måde, er der ingen præcisionsmodifikator. [19]

Størrelsesmodifikator

Størrelsesfeltet giver dig mulighed for at angive størrelsen af ​​de data, der sendes til funktionen. Behovet for dette felt forklares af det særlige ved at overføre et vilkårligt antal parametre til en funktion på C-sproget: Funktionen kan ikke "uafhængigt" bestemme typen og størrelsen af ​​de overførte data, så information om typen af ​​parametre og deres eksakt størrelse skal beståes eksplicit.

I betragtning af størrelsesspecifikationernes indflydelse på formateringen af ​​heltalsdata skal det bemærkes, at der i C- og C++-sprogene er en kæde af par af signerede og usignerede heltaltyper, som i ikke-faldende størrelsesorden er arrangeret som følger:

signeret type Usigneret type
signeret char usigneret char
underskrevet kort ( kort ) unsigned short int ( unsigned short )
underskrevet int ( int ) usigneret int ( usigneret )
underskrevet lang int ( lang ) unsigned long int ( unsigned long )
underskrevet lang lang int ( lang lang ) unsigned long long int ( unsigned long long )

De nøjagtige størrelser af typerne er ukendte, med undtagelse af fortegnede char og usignerede char- typer .

Parrede signerede og usignerede typer har samme størrelse, og værdier, der kan repræsenteres i begge typer, har den samme repræsentation i dem.

Char -typen har samme størrelse som de signerede char -typer og usignerede char -typer og deler et sæt af repræsentative værdier med en af ​​disse typer. Det antages endvidere, at char  er et andet navn for en af ​​disse typer; en sådan antagelse er acceptabel for nærværende betragtning.

Derudover har C typen _Bool , mens C++ har bool -typen .

Når du sender argumenter til en funktion, der ikke svarer til de formelle parametre i funktionsprototypen (som alle er argumenter, der indeholder outputværdier), gennemgår disse argumenter standardpromoveringer , nemlig:

  • float - argumenter castes til det dobbelte ;
  • argumenter af typer usigneret char , unsigned short , signed char og short castes til en af ​​følgende typer:
    • int hvis denne type er i stand til at repræsentere alle værdier af den originale type, eller
    • usigneret ellers;
  • argumenter af typen _Bool eller bool castes til typen int .

Printf-funktioner kan således ikke tage argumenter af typen float , _Bool , eller bool , eller heltalstyper mindre end int eller unsigned .

Det anvendte sæt af størrelsesspecifikationer afhænger af typespecifikationen (se nedenfor).

specificator %d, %i, %o, %u, %x_%X %n Bemærk
mangler int eller usigneret int pointer til int
l long int eller unsigned long int pointer til lang int
hh Argumentet er af typen int eller unsigned int , men er tvunget til at skrive henholdsvis signed char eller unsigned char . pointer til signeret char formelt eksisteret i C siden 1999-standarden og i C++ siden 2011-standarden.
h Argumentet er af typen int eller unsigned int , men er tvunget til at skrive henholdsvis short int eller unsigned short int . pointer til kort int
ll lang lang int eller usigneret lang lang int pointer til lang lang int
j intmax_t eller uintmax_t pointer til intmax_t
z size_t (eller størrelsesækvivalent signeret type) pointer til en signeret type , der i størrelse svarer til size_t
t ptrdiff_t (eller en tilsvarende usigneret type) pointer til ptrdiff_t
L __int64 eller usigneret __int64 pointer til __int64 For Borland Builder 6 (specifikationen llforventer et 32-bit nummer)

Specifikationer hog hhbruges til at kompensere for standardtypekampagner i forbindelse med overgange fra signerede til usignerede typer eller omvendt.

Overvej f.eks. en C-implementering, hvor char -typen er signeret og har en størrelse på 8 bit, int -typen har en størrelse på 32 bit, og en yderligere måde at indkode negative heltal på.

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

Et sådant opkald vil producere output FFFFFFFF, som måske ikke er, hvad programmøren forventede. Faktisk er værdien af ​​c (char)(-1) , og efter typepromovering er den -1 . Anvendelse af formatet %Xfår den givne værdi til at blive fortolket som usigneret, det vil sige 0xFFFFFFFF .

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

Disse to opkald har samme effekt og producerer output FF. Den første mulighed giver dig mulighed for at undgå fortegnsmultiplikationen, når du promoverer typen, den anden kompenserer for den allerede "inde i" printf- funktionen .

specificator %a, %A, %e, %E, %f, %F, %g_%G
mangler dobbelt
L lang dobbelt
specificator %c %s
mangler Argumentet er af typen int eller unsigned int , men er tvunget til at skrive char char*
l Argumentet er af typen wint_t , men er tvunget til at skrive wchar_t wchar_t*
Typespecifikation

Typen angiver ikke kun værdiens type (fra C-programmeringssprogets synspunkt), men også den specifikke repræsentation af outputværdien (f.eks. kan tal vises i decimal eller hexadecimal form). Skrevet som et enkelt tegn. I modsætning til andre felter er det obligatorisk. Den maksimale understøttede outputstørrelse fra en enkelt escape-sekvens er efter standarder mindst 4095 tegn; i praksis understøtter de fleste compilere væsentligt større mængder data.

Typeværdier:

  • d , i  — fortegnet decimaltal, standardtypen er int . Som standard skrives det med højrejustering, tegnet skrives kun for negative tal. I modsætning til funktionerne i scanf- familien , for funktionerne i printf -familien, er %d- og %i - specifikationerne fuldstændig synonyme;
  • o  — usigneret oktaltal, standardtypen er usigneret int ;
  • u  er et usigneret decimaltal, standardtypen er usigneret int ;
  • x og X  er hexadecimale tal uden fortegn, x bruger små bogstaver (abcdef), X bruger store bogstaver (ABCDEF), standardtypen er uden fortegn int ;
  • f og F  er flydende kommatal, standardtypen er dobbelt . Som standard udsendes de med en præcision på 6, hvis modulo-tallet er mindre end 1, skrives et 0 før decimaltegnet. Værdier af ±∞ præsenteres i formen [-]inf eller [-]uendelig (afhængigt af platformen); værdien af ​​Nan er repræsenteret som [-]nan eller [-]nan(en hvilken som helst tekst nedenfor) . Ved at bruge F udskrives de angivne værdier med store bogstaver ( [-]INF , [-]INFINITY , NAN ).
  • e og E  er flydende kommatal i eksponentiel notation (af formen 1.1e+44), standardtypen er dobbelt . e udsender tegnet "e" med små bogstaver, E  - med store bogstaver (3.14E+0);
  • g og G  er et flydende kommatal, standardtypen er dobbelt . Repræsentationsformen afhænger af mængdens værdi ( f eller e ). Formatet afviger en smule fra flydende komma ved, at foranstillede nuller til højre for decimalkommaet ikke udskrives. Desuden vises semikolondelen ikke, hvis tallet er et heltal;
  • a og A (startende fra C-sprogstandarderne fra 1999 og C++ fra 2011) — et flydende decimaltal i hexadecimal form, standardtypen er dobbelt ;
  • c  — output af symbolet med koden svarende til det beståede argument, standardtypen er int ;
  • s  - output af en streng med en nul-terminerende byte; hvis længdemodifikatoren er l , udsendes strengen wchar_t* . På Windows afhænger værdierne af type s af typen af ​​anvendte funktioner. Hvis der bruges en familie af printffunktioner, så betegner s strengen char* . Hvis en familie af wprintffunktioner bruges, så betegner s strengen wchar_t* .
  • S  er det samme som s med længdemodifikator l ; På Windows afhænger værdien af ​​type S af typen af ​​anvendte funktioner. Hvis en familie af printffunktioner bruges, så står S for strengen wchar_t* . Hvis en familie af wprintffunktioner bruges, så betegner S strengen char* .
  • p - pointer  output , kan udseendet variere betydeligt afhængigt af den interne repræsentation i compileren og platformen (for eksempel bruger 16-bit MS-DOS-platformen notationen af ​​formen FFEC:1003, 32-bit platformen med flad adressering bruger adressen af formularen 00FA0030);
  • n  - optag ved pointer, sendt som argument, antallet af tegn skrevet på tidspunktet for forekomsten af ​​kommandosekvensen indeholdende n ;
  • %  - tegn for at vise procenttegnet (%), bruges til at aktivere output af procenttegn i printf-strengen, altid brugt i formen %%.
Output af flydende kommatal

Afhængigt af den aktuelle lokalitet kan både et komma og et punktum (og muligvis et andet symbol) bruges ved visning af flydende kommatal. Printfs adfærd med hensyn til tegnet, der adskiller brøk- og heltalsdelen af ​​tallet, bestemmes af den anvendte lokalitet (mere præcist variablen LC NUMERIC ). [tyve]

Specielle makroer til et udvidet sæt af heltalsdatatypealiasser

Second C Standard (1999) giver et udvidet sæt af aliaser for heltalsdatatyper int N _t , uint N _t , int_least N _t , uint_least N _t , int_fast N _t , uint_fast N _t (hvor N er den nødvendige bitdybde), intptr_t , uintptr_t , intmax_t , uintmax_t .

Hver af disse typer matcher muligvis ikke nogen af ​​de standard indbyggede heltaltyper. Formelt set, når programmøren skriver bærbar kode, ved han ikke på forhånd, hvilken standard eller udvidet størrelsesspecifikation han skal anvende.

int64_t x = 100000000000 ; int bredde = 20 ; printf ( "%0*lli" , bredde , x ); Forkert, fordi int64_t muligvis ikke er det samme som long long int .

For at være i stand til at udlede værdierne af objekter eller udtryk af disse typer på en bærbar og bekvem måde, definerer implementeringen for hver af disse typer et sæt makroer, hvis værdier er strenge, der kombinerer størrelse og typespecifikationer.

Makronavne er som følger:

Et par signerede og usignerede typer Makro navn
int N_t og uint N_t _ _ PRITN
int_mindst N _t og uint_mindst N _t PRITLEASTN
int_fastN_t og uint_fastN_t _ _ _ _ PRITFASTN
intmax_t og uintmax_t PRITMAX
intptr_t og uintptr_t PRITPTR

Her T er en af ​​følgende typespecifikationer: d, i, u, o, x, X.

int64_t x = 100000000000 ; int bredde = 20 ; printf ( "%0*" PRIi64 , bredde , x ); Den korrekte måde at udlæse en værdi af typen int64_t i C-sprog.

Du bemærker måske, at typerne intmax_t og uintmax_t har en standardstørrelsesspecifikation j, så makroen er højst sandsynligt altid defineret som . PRITMAX"jT"

XSI-udvidelser i Single Unix-standarden

Under Single UNIX- standarden (stort set svarende til POSIX- standarden ) er følgende tilføjelser til printf defineret i forhold til ISO C, under XSI -udvidelsen (X/Open System Interface):

  • Evnen til at udlæse en vilkårlig parameter efter tal tilføjes (angivet n$umiddelbart efter tegnet i begyndelsen af ​​kontrolsekvensen, f.eks. printf("%1$d:%2$.*3$d:%4$.*3$d\n", hour, min, precision, sec);).
  • Tilføjet flag "'" (enkelt anførselstegn), som for typer d , i , o , u foreskriver at adskille klasser med det tilsvarende tegn.
  • type C svarende til lc ISO C (tegnoutput af typen wint_t ).
  • type S svarende til ls ISO C (strengoutput som wchar_t* )
  • Tilføjet fejlkoder EILSEQ, EINVAL, ENOMEM, EOVERFLOW.

Ikke-standardudvidelser

GNU C bibliotek

GNU C-biblioteket ( libc ) tilføjer følgende udvidelser:

  • type m udskriver værdien af ​​den globale variabel errno (fejlkoden for den sidste funktion).
  • type C svarer til lc .
  • flaget ' (enkelt anførselstegn) bruges til at adskille klasser ved udskrivning af tal. Separationsformatet afhænger af LC_NUMERIC
  • størrelsen af ​​q angiver typen long long int (på systemer, hvor long long int ikke understøttes , er dette det samme som long int
  • størrelse Z er et alias for z , blev introduceret i libc før fremkomsten af ​​C99-standarden , og anbefales ikke til brug i ny kode.
Registrering af dine egne typer

GNU libc understøtter brugerdefineret typeregistrering, hvilket gør det muligt for programmøren at definere outputformatet for deres egne datastrukturer. For at registrere en ny type skal du bruge funktionen
int register_printf_function (int type, printf_function handler-function, printf_arginfo_function arginfo-function), hvor:

  • type  — bogstav for typen (hvis type = 'Y', så vil opkaldet se ud som '%Y');
  • handler-function  - en pointer til en funktion, der kaldes af printf-funktioner, hvis den type, der er angivet i type , stødes på i formatstrengen ;
  • arginfo-funktion  er en pointer til en funktion, der vil blive kaldt af funktionen parse_printf_format .

Ud over at definere nye typer, tillader registrering eksisterende typer (såsom s , i ) at blive omdefineret.

Microsoft Visual C

Microsoft Visual Studio til C/C++ programmeringssprogene i formatet af printf-specifikationen (og andre familiefunktioner) giver følgende udvidelser:

  • størrelse boks:
feltværdi type
I32 underskrevet __int32 , usigneret __int32
I64 underskrevet __int64 , usigneret __int64
jeg ptrdiff_t , størrelse_t
w svarende til l for strenge og tegn
ahorn

Maple -matematikmiljøet har også en printf-funktion, der har følgende funktioner:

Formatering
    • %a, %A: Maple-objektet vil blive returneret i tekstnotation, dette virker for alle objekter (f.eks. matricer, funktioner, moduler osv.). Det lille bogstav instruerer om at omgive tegn (navne) med backticks, der skal være omgivet af backticks i input til printf.
    • %q, %Q: samme som %a/%A, men ikke kun ét argument vil blive behandlet, men alt fra det, der matcher formateringsflaget. Således kan %Q/%q-flaget kun vises sidst i formatstrengen.
    • %m: Formater objektet i henhold til dets interne ahornrepræsentation. Praktisk taget brugt til at skrive variabler til en fil.

Eksempel:

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

Maples fprintf-funktion tager enten en fildeskriptor (returneret af fopen) eller et filnavn som sit første argument. I sidstnævnte tilfælde skal navnet være af typen "symbol", hvis filnavnet indeholder punktum, så skal det være omgivet af backticks eller konverteret med konverter (filnavn, symbol) funktionen.

Sårbarheder

Funktionerne i printf- familien tager en liste over argumenter og deres størrelse som en separat parameter (i formatstrengen). Et misforhold mellem formatstrengen og de beståede argumenter kan føre til uforudsigelig adfærd, stakkorruption, vilkårlig kodeudførelse og ødelæggelse af dynamiske hukommelsesområder. Mange funktioner i familien kaldes "unsafe" ( engelsk  unsafe ), da de ikke engang har den teoretiske evne til at beskytte sig mod ukorrekte data.

Funktioner i s -familien (uden n , såsom sprintf , vsprintf ) har heller ingen grænser for den maksimale størrelse af den skrevne streng og kan føre til en bufferoverløbsfejl (når data skrives uden for det tildelte hukommelsesområde).

Opførsel, når formatstreng og beståede argumenter ikke matcher

Som en del af opkaldskonventionen udførescdecl stakoprydning af opkaldsfunktionen. Når printf kaldes , placeres argumenterne (eller pegepindene til dem) i den rækkefølge, de er skrevet i (venstre mod højre). Efterhånden som formatstrengen behandles, læser printf -funktionen argumenter fra stakken. Følgende situationer er mulige:

  • antallet og typen af ​​argumenter matcher dem, der er angivet i formatstrengen (normal funktionsoperation)
  • flere argumenter sendt til funktionen end angivet i formatstrengen (ekstra argumenter)
  • Færre argumenter sendt til funktionen end krævet af formatstrengen (utilstrækkelige argumenter)
  • Forkerte størrelsesargumenter blev sendt til funktion
  • Argumenter af den korrekte størrelse, men den forkerte type, blev videregivet til funktionen

C-sprogspecifikationerne beskriver kun to situationer (normal drift og ekstra argumenter). Alle andre situationer er fejlagtige og fører til udefineret programadfærd (fører i virkeligheden til vilkårlige resultater, op til udførelse af uplanlagte kodesektioner).

For mange argumenter

Når du sender et for stort antal argumenter, læser printf -funktionen de argumenter, der kræves for at behandle formatstrengen korrekt og vender tilbage til den kaldende funktion. Den kaldende funktion rydder i overensstemmelse med specifikationen stakken fra de parametre, der er overført til den kaldte funktion. I dette tilfælde bruges de ekstra parametre simpelthen ikke, og programmet fortsætter uden ændringer.

Ikke nok argumenter

Hvis der er færre argumenter på stakken, når printf kaldes , end der kræves for at behandle formatstrengen, så læses de manglende argumenter fra stakken, på trods af at der er vilkårlige data på stakken (ikke relevant for arbejdet med printf ) . Hvis databehandlingen var "succesfuld" (dvs. den afsluttede ikke programmet, hængte eller skrev til stakken), efter returnering til den kaldende funktion, returneres værdien af ​​stakmarkøren til dens oprindelige værdi, og programmet fortsætter.

Ved behandling af "ekstra" stakværdier er følgende situationer mulige:

  • Vellykket læsning af en "ekstra" parameter for output (tal, pointer, symbol osv.) - den "næsten tilfældige" værdi, der er læst fra stakken, placeres i outputresultaterne. Dette udgør ikke en fare for programmets drift, men kan føre til kompromittering af nogle data (output af stakværdier, som en angriber kan bruge til at analysere programmets drift og få adgang til programmets interne/private information).
  • En fejl ved læsning af en værdi fra stakken (for eksempel som et resultat af udtømning af de tilgængelige stakværdier eller adgang til "ikke-eksisterende" hukommelsessider) - en sådan fejl vil højst sandsynligt få programmet til at gå ned.
  • Læsning af en pointer til en parameter. Strenge sendes ved hjælp af en pointer, når man læser "vilkårlig" information fra stakken, bruges den læste (næsten tilfældige) værdi som en pointer til et tilfældigt hukommelsesområde. Programmets opførsel i dette tilfælde er udefineret og afhænger af indholdet af dette hukommelsesområde.
  • At skrive en parameter ved hjælp af pointer ( %n) - i dette tilfælde ligner adfærden situationen med læsning, men det kompliceres af mulige bivirkninger ved at skrive til en vilkårlig hukommelsescelle.
Argumenttype uoverensstemmelse

Formelt forårsager enhver uoverensstemmelse mellem typen af ​​argumentet og forventningen programmets udefinerede adfærd. I praksis er der flere cases, der er særligt interessante set ud fra programmeringspraksis:

  • Argumentet er af samme type som forventet, men af ​​en anden størrelse.
  • Argumentet har samme størrelse som forventet, men en anden type.

Andre tilfælde fører som regel til åbenlyst ukorrekt adfærd og er let opdaget.

Heltal eller flydende komma argument størrelse uoverensstemmelse

For et heltalsargument (med en heltalsformatspecifikation) er følgende situationer mulige:

  • Passer parametre, der er større end forventet (læser de mindre fra de større). I dette tilfælde kan den viste værdi, afhængigt af den accepterede byte-rækkefølge og retningen af ​​stakvækst, enten falde sammen med værdien af ​​argumentet eller vise sig ikke at være relateret til det.
  • Passer parametre, der er mindre end forventet (læser større fra mindre). I dette tilfælde er en situation mulig, når stakområder, der går ud over grænserne for de beståede argumenter, læses. Funktionens adfærd i dette tilfælde ligner adfærden i en situation med mangel på parametre. Generelt svarer outputværdien ikke til den forventede værdi.

For et reelt argument (med en reel formatspecifikation), for enhver størrelsesmismatch, svarer outputværdien som regel ikke til den beståede værdi.

Som regel, hvis størrelsen af ​​et argument er forkert, bliver den korrekte behandling af alle efterfølgende argumenter umulig, da der indføres en fejl i pointeren til argumenterne. Denne effekt kan dog udlignes ved at justere værdier på stakken.

Justering af værdier på stakken

Mange platforme har regler for justering af heltal og/eller reel værdi, som kræver (eller anbefaler), at de placeres på adresser, der er multipla af deres størrelse. Disse regler gælder også for at sende funktionsargumenter på stakken. I dette tilfælde kan en række uoverensstemmelser i typerne af forventede og faktiske parametre gå ubemærket hen, hvilket skaber illusionen om et korrekt program.

uint32_t a = 1 ; uint64_t b = 2 , c = 3 ; printf ( "%" PRId64 "%" PRId64 "%" PRId64 , b , a , c ); I dette eksempel har den faktiske atypeparameter uint32_ten ugyldig formatspecifikation knyttet %"PRId64"til typen uint64_t. På nogle platforme med en 32-bit-type intkan fejlen dog forblive ubemærket, afhængigt af den accepterede byte-rækkefølge og retningen af ​​stakvækst. De faktiske parametre bog cvil blive justeret på en adresse, der er et multiplum af deres størrelse (dobbelt så stor som a). Og "mellem" værdierne avil bder være et tomt (normalt nulstillet) mellemrum på 32 bit i størrelse; når styklisten behandles, vil %"PRId64"32-bit værdien asammen med dette hvide mellemrum blive fortolket som en enkelt 64-bit værdi.

En sådan fejl kan uventet dukke op, når programkoden overføres til en anden platform, ændres compiler eller kompileringstilstand.

Potentiel størrelsesforskel

Definitionerne af C- og C++-sprogene beskriver kun de mest generelle krav til størrelsen og repræsentationen af ​​datatyper. Derfor viser repræsentationen af ​​nogle formelt forskellige datatyper sig på mange platforme at være den samme. Dette medfører, at nogle typer uoverensstemmelser ikke bliver opdaget i lang tid.

For eksempel på Win32-platformen er det generelt accepteret, at størrelserne af typer intog long inter de samme (32 bit). Således vil opkaldet printf("%ld", 1)eller printf("%d", 1L)blive udført "korrekt".

En sådan fejl kan uventet dukke op, når programkoden overføres til en anden platform, ændres compiler eller kompileringstilstand.

Når man skriver programmer i C++-sproget, skal man være forsigtig med at udlede værdierne af variabler, der er erklæret ved hjælp af heltalstypealiaser, især size_t, og ptrdiff_t; den formelle definition af C++-standardbiblioteket refererer til den første C-standard (1990). Second C Standard (1999) definerer størrelsesspecifikationer for typer size_tog og for en række andre typer til brug med lignende objekter. ptrdiff_tMange C++ implementeringer understøtter dem også.

størrelse_t s = 1 ; printf ( "%u" , s ); Dette eksempel indeholder en fejl, der kan opstå på platforme, sizeof (unsigned int)hvor sizeof (size_t). størrelse_t s = 1 ; printf ( "%zu" , s ); Den korrekte måde at udlede værdien af ​​et typeobjekt på er size_ti C-sprog. Type uoverensstemmelse, når størrelse matcher

Hvis argumenterne er af samme størrelse, men har en anden type, vil programmet ofte køre "næsten korrekt" (vil ikke forårsage hukommelsesadgangsfejl), selvom outputværdien sandsynligvis er meningsløs. Det skal bemærkes, at blandingen af ​​parrede heltalstyper (med og uden fortegn) er tilladt, ikke forårsager udefineret adfærd og nogle gange bruges bevidst i praksis.

Når du bruger en formatspecifikation %s, vil en argumentværdi af et heltal, et reelt eller en anden pointertype end char*, blive fortolket som adressen på en streng. Denne adresse, generelt set, kan vilkårligt pege på et ikke-eksisterende eller utilgængeligt hukommelsesområde, hvilket vil føre til en hukommelsesadgangsfejl, eller til et hukommelsesområde, der ikke indeholder en linje, hvilket vil føre til nonsens-output, muligvis meget stort .

Formatstrengssårbarhed

Da printf (og andre funktioner i familien) kan udskrive teksten i formatstrengen uden ændringer, hvis den ikke indeholder escape-sekvenser, så er tekstoutputtet af kommandoen muligt
printf(text_to_print);
Hvis text_to_print er hentet fra eksterne kilder (læs fra en fil) , modtaget fra brugeren eller operativsystemet), så kan tilstedeværelsen af ​​et procenttegn i den resulterende streng føre til ekstremt uønskede konsekvenser (op til programmet fryser).

Forkert kodeeksempel:
printf(" Current status: 99% stored.");
Dette eksempel indeholder en escape-sekvens "% s" , der indeholder escape-sekvenstegnet (%), et flag (mellemrum) og en strengdatatype ( s ). Funktionen, efter at have modtaget kontrolsekvensen, vil forsøge at læse markøren til strengen fra stakken. Da der ikke blev sendt yderligere parametre til funktionen, er værdien, der skal læses fra stakken, udefineret. Den resulterende værdi vil blive fortolket som en pointer til en null-termineret streng. Outputtet af en sådan "streng" kan føre til et vilkårligt hukommelsesdump, en hukommelsesadgangsfejl og en stakkorruption. Denne type sårbarhed kaldes et formatstrengangreb .  [21]

Bufferoverløb

Printf - funktionen , når der udskrives et resultat, er ikke begrænset af det maksimale antal output-tegn. Hvis der som følge af en fejl eller forglemmelse vises flere tegn end forventet, er det værste, der kan ske, "ødelæggelsen" af billedet på skærmen. Oprettet i analogi med printf , var sprintf- funktionen heller ikke begrænset i den maksimale størrelse af den resulterende streng. Men i modsætning til den "uendelige" terminal er den hukommelse, som applikationen allokerer til den resulterende streng, altid begrænset. Og i tilfælde af at gå ud over de forventede grænser, sker optagelsen i hukommelsesområder, der tilhører andre datastrukturer (eller generelt i utilgængelige hukommelsesområder, hvilket betyder, at programmet går ned på næsten alle platforme). At skrive til vilkårlige områder af hukommelsen fører til uforudsigelige effekter (som kan dukke op meget senere og ikke i form af en programfejl, men i form af korruption af brugerdata). Manglen på en grænse for den maksimale strengstørrelse er en grundlæggende planlægningsfejl ved udvikling af en funktion. Det er på grund af dette, at funktionerne sprintf og vsprintf har den usikre status . I stedet udviklede han funktionerne snprintf , vsnprintf , som tager et ekstra argument, der begrænser den maksimale resulterende streng. Funktionen swprintf , som dukkede op meget senere (til at arbejde med multi-byte-kodninger), tager denne mangel i betragtning og tager et argument for at begrænse den resulterende streng. (Derfor er der ingen snwprintf- funktion ).

Et eksempel på et farligt kald til sprintf :

kulbuffer[65536]; char* navn = get_brugernavn_fra_tastatur(); sprintf(buffer, "Brugernavn:%s", navn);

Ovenstående kode forudsætter implicit, at brugeren ikke vil skrive 65 tusind tegn på tastaturet, og bufferen "burde være nok". Men brugeren kan omdirigere input fra et andet program eller stadig indtaste mere end 65.000 tegn. I dette tilfælde vil hukommelsesområder blive ødelagt, og programmets adfærd bliver uforudsigelig.

Vanskeligheder ved brug

Manglende typekontrol

Funktionerne i printf -familien bruger C -datatyper . Størrelsen af ​​disse typer og deres forhold kan variere fra platform til platform. For eksempel på 64-bit platforme, afhængigt af den valgte model ( LP64 , LLP64 eller ILP64 ), kan størrelserne af int og lange typer variere. Hvis programmøren indstiller formatstrengen til "næsten korrekt", vil koden virke på én platform og give det forkerte resultat på en anden (i nogle tilfælde kan det muligvis føre til datakorruption).

For eksempel fungerer koden printf( "text address: 0x%X", "text line" );korrekt på en 32-bit platform ( ptrdiff_t størrelse og int størrelse 32 bit) og på en 64-bit IPL64 model (hvor ptrdiff_t og int størrelser er 64 bit), men vil give et forkert resultat på en 64 -bit platform af en LP64 eller LLP64 model, hvor størrelsen af ​​ptrdiff_t er 64 bit og størrelsen af ​​int er 32 bit. [22]

I Oracle Javaprintf bruges indpakkede typer med dynamisk identifikation i analogen af ​​en funktion , [6] i Embarcadero Delphi  - et mellemlag array of const, [23] i forskellige implementeringer i C ++ [24]  - overbelastning af operationer , i C + + 20  - variable skabeloner. Derudover specificerer formaterne ( %d, %fosv.) ikke argumentets type, men kun outputformatet, så ændring af typen af ​​argumentet kan forårsage en nødsituation eller bryde logikken på højt niveau (f.eks. "bryde" layout af bordet) - men ikke ødelægge hukommelsen.

Manglende standardisering

Problemet forværres af utilstrækkelig standardisering af formatstrenge i forskellige compilere: for eksempel understøttede tidlige versioner af Microsoft-biblioteker ikke "%lld"(du skulle angive "%I64d"). Der er stadig en opdeling mellem Microsoft og GNU efter type size_t: %Iuførstnævnte og %zusidstnævnte. GNU C kræver ikke en swprintfmaksimal strenglængde i en funktion (du skal skrive snwprintf).

Manglende evne til at omarrangere argumenter

Familiefunktionerne printfer praktiske til softwarelokalisering : for eksempel er det nemmere at oversætte«You hit %s instead of %s.» end strengsnippets «You hit »og « instead of ». «.»Men også her er der et problem: det er umuligt at omarrangere de substituerede strenge steder for at få: «Вы попали не в <2>, а в <1>.».

De udvidelser printf, der bruges i Oracle Java og Embarcadero Delphi , giver dig stadig mulighed for at omarrangere argumenterne.

printf værktøj

Inden for POSIX -standarden er printf -værktøjet beskrevet , som formaterer argumenter i overensstemmelse med det relevante mønster, svarende til printf -funktionen .

Værktøjet har følgende opkaldsformat: , hvor printf format [argument …]

  • format  er en formatstreng, der i syntaks ligner printf -funktionen formatstreng .
  • argument  er en liste over argumenter (0 eller flere) skrevet i strengform.

Implementeringseksempler

Eksempel 1 C (programmeringssprog)

#include <stdio.h> #include <locale.h> #define PI 3.141593 int main () { setlocale ( LC_ALL , "RUS" ); int tal = 7 ; flydetærter = 12,75 ; _ int pris = 7800 ; printf ( "%d deltagere spiste %f kirsebærtærter. \n " , antal , tærter ); printf ( "Værdien af ​​pi er %f \n " , PI ); printf ( "Farvel! Din kunst koster for meget (%c%d) \n " , '$' , 2 * pris ); returnere 0 ; }

Eksempel 2 C (programmeringssprog)

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

Eksempel 3 C (programmeringssprog)

#include <stdio.h> #define BLURB "Autentisk efterligning!" int main () { const dobbelt LEJE = 3852,99 ; printf ( "*%8f* \n " , LEJ ); printf ( "*%e* \n " , LEJE ); printf ( "*%4.2f* \n " , LEJ ); printf ( "*%3.1f* \n " , LEJ ); printf ( "*%10.3f* \n " , LEJ ); printf ( "*%10.3E* \n " , LEJ ); printf ( "*%+4.2f* \n " , LEJ ); 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 ); returnere 0 ; } /* resultat *3852,990000* *3,852990e+03* *3852,99* *3853,0* * 3852,990* * 3,853E+03* *+3852,99* 1f 1F 0x1f **0x1f **-42** ** 0x1f **-42 ** 6 **00006** 006** [Autentisk imitation!] [Autentisk efterligning!] [Authe] [Authe ] */

Links

  1. Kort beskrivelse af BCPL-sproget . Hentet 16. december 2006. Arkiveret fra originalen 9. december 2006.
  2. B Sprogguide Arkiveret 6. juli 2006.
  3. Beskrivelse af sprintf- funktionen i Perl-dokumentationen . Hentet 12. januar 2007. Arkiveret fra originalen 14. januar 2007.
  4. En beskrivelse af formateringsoperatoren for strengtyper i Python Arkiveret 9. november 2006.
  5. Beskrivelse af PHP 's printf -funktion . Hentet 23. oktober 2006. Arkiveret fra originalen 6. november 2006.
  6. 1 2 Beskrivelse af funktionen java.io.PrintStream.printf() i Java 1.5 . Hentet 12. januar 2007. Arkiveret fra originalen 13. januar 2007.
  7. Beskrivelse af printf- funktionen i Ruby-dokumentationen . Hentet 3. december 2006. Arkiveret fra originalen 5. december 2006.
  8. Beskrivelse af string.format- funktionen i Lua-dokumentationen . Dato for adgang: 14. januar 2010. Arkiveret fra originalen 15. november 2013.
  9. Beskrivelse af formatfunktionen i TCL-dokumentationen . Hentet 14. april 2008. Arkiveret fra originalen 4. juli 2007.
  10. Beskrivelse af strengmønsteret for printf i GNU Octave-dokumentationen . Hentet 3. december 2006. Arkiveret fra originalen 27. oktober 2006.
  11. Beskrivelse af printf i Maple-dokumentationen{{subst:AI}}
  12. R. Fourer, D. M. Gay og B. W. Kernighan. AMPL: A Modeling Language for Mathematical Programming, 2. udgave. Pacific Grove, CA: Brooks/Cole--Thomson Learning, 2003.
  13. GNU Emacs Lisp Reference Manual, Formatting Strings Arkiveret 27. september 2007 på Wayback Machine
  14. Beskrivelse af Printf- modulet i OCaml-dokumentationen . Hentet 12. januar 2007. Arkiveret fra originalen 13. januar 2007.
  15. Beskrivelse af Printf- modulet i Haskell-dokumentationen . Hentet 23. juni 2015. Arkiveret fra originalen 23. juni 2015.
  16. std::println! - Rust . doc.rust-lang.org. Hentet 24. juli 2016. Arkiveret fra originalen 18. august 2016.
  17. format . www.freepascal.org. Hentet 7. december 2016. Arkiveret fra originalen 24. november 2016.
  18. fmt - The Go-programmeringssprog . golang.org. Hentet 25. marts 2020. Arkiveret fra originalen 4. april 2020.
  19. §7.19.6.1 ISO/IEC 9899:TC2
  20. § 7.11.1.1 ISO/IEC 9899:TC2, LC_NUMERIC definerer især repræsentationsformen for decimalseparatoren.
  21. Printf Vulnerability Description, Robert C. Seacord: Sikker kodning i C og C++. Addison Wesley, september 2005. ISBN 0-321-33572-4
  22. Beskrivelse af problemerne med at portere applikationer fra 32 til 64 bit arkitektur . Hentet 14. december 2006. Arkiveret fra originalen 8. marts 2007.
  23. System.SysUtils.Format Arkiveret 11. januar 2013 på Wayback Machine 
  24. For eksempel boost::formatdokumentation Arkiveret 26. marts 2013 på Wayback Machine 

Kilder

  • 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 - pages på Linux [13] , i libc- dokumentationen [14]
  • Se libc -manualen [15] for en beskrivelse af formatstrengens syntaks .
  • Beskrivelse af formatstrengen i dokumentationen til Microsoft Visual Studio 2005 [16]
  • Beskrivelse af register_printf_function [17] , [18]
  • Programmeringssprog C. Forelæsninger og øvelser. Forfatter: Stephen Prata. ISBN 978-5-8459-1950-2 , 978-0-321-92842-9; 2015

Se også