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.
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
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 .
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.).
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.
Alle funktioner har stammen printf i deres navn . Præfikserne før funktionsnavnet betyder:
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.
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.
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 %%.
Formatspecifikationen ser sådan ud:
% [ flag ][ width ][ . præcision ][ størrelse ] typeDe nødvendige komponenter er formatspecifikationens starttegn ( %) og typen .
FlagSkilt | 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å. |
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øjagtighedsmodifikatorPræ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ørrelsesmodifikatorStø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:
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* |
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:
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 heltalsdatatypealiasserSecond 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"
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):
GNU C-biblioteket ( libc ) tilføjer følgende udvidelser:
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:
Ud over at definere nye typer, tillader registrering eksisterende typer (såsom s , i ) at blive omdefineret.
Microsoft Visual CMicrosoft Visual Studio til C/C++ programmeringssprogene i formatet af printf-specifikationen (og andre familiefunktioner) giver følgende udvidelser:
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 |
Maple -matematikmiljøet har også en printf-funktion, der har følgende funktioner:
FormateringEksempel:
> printf("%a =%A", `+`, `+`); `+` = + > printf("%a =%m", `+`, `+`); `+` = I"+f*6"F$6#%(builtinGF$"$Q"F$F$F$F"%*protectedG KonklusionMaples 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.
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).
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:
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 argumenterNå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 argumenterHvis 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:
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:
Andre tilfælde fører som regel til åbenlyst ukorrekt adfærd og er let opdaget.
Heltal eller flydende komma argument størrelse uoverensstemmelseFor et heltalsargument (med en heltalsformatspecifikation) er følgende situationer mulige:
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å stakkenMange 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ørrelsesforskelDefinitionerne 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 matcherHvis 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 .
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]
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.
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.
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).
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.
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 …]
Unix-kommandoer | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
|