printf (din engleză print formatted , "formatted printing") - un nume generalizat pentru o familie de funcții sau metode de biblioteci comerciale standard sau binecunoscute sau operatori încorporați ai unor limbaje de programare utilizate pentru ieșirea formatată - ieșire în diverse fluxuri de valori de diferite tipuri formatate în funcție de un șablon dat. Acest șablon este determinat de un șir compus după reguli speciale (șir de format).
Cel mai notabil membru al acestei familii este funcția printf , precum și o serie de alte funcții derivate din printfnume din biblioteca standard C (care face, de asemenea, parte din bibliotecile standard C++ și Objective-C ).
Familia UNIX de sisteme de operare are, de asemenea, un utilitar printf care servește aceleași scopuri de ieșire formatată.
Operatorul FORMAT al lui Fortran poate fi considerat un prototip timpuriu al unei astfel de funcții . Funcția de inferență condusă de șiruri a apărut în precursorii limbajului C ( BCPL și B ). În specificația bibliotecii standard C , a primit cea mai cunoscută formă (cu steaguri, lățime, precizie și dimensiune). Sintaxa șirului șablonului de ieșire (numită uneori șir de format , șir de format sau șir de format ) a fost utilizată ulterior de alte limbaje de programare (cu variații pentru a se potrivi caracteristicilor acestor limbaje). De regulă, funcțiile corespunzătoare acestor limbi sunt numite și printf și/sau derivate ale acesteia.
Unele medii de programare mai recente (cum ar fi .NET ) folosesc, de asemenea, conceptul de ieșire bazată pe șiruri de format, dar cu o sintaxă diferită.
Fortran Am avut deja operatori care au furnizat rezultate formatate. Sintaxa instrucțiunilor WRITE și PRINT includea o etichetă care se referă la o instrucțiune FORMAT neexecutabilă care conținea o specificație de format. Specificatorii făceau parte din sintaxa operatorului, iar compilatorul putea genera imediat cod care realizează direct formatarea datelor, ceea ce asigura cea mai bună performanță pe computerele acelor vremuri. Cu toate acestea, au existat următoarele dezavantaje:
Primul prototip al viitoarei funcție printf apare în limbajul BCPL în anii 1960 . Funcția WRITEF ia un șir de format care specifică tipul de date separat de datele în sine din variabila șir (tipul a fost specificat fără câmpurile steag, lățime, precizie și dimensiune, dar era deja precedat de un semn de procent %). [1] Scopul principal al șirului de format a fost acela de a trece tipuri de argumente (în limbaje de programare cu tastare statică , determinarea tipului de argument transmis pentru o funcție cu o listă nefixată de parametri formali necesită un mecanism complex și ineficient pentru transmiterea informaţiilor de tip în cazul general). Funcția WRITEF în sine a fost un mijloc de simplificare a ieșirii: în loc de un set de funcții WRCH (ieșire un caracter), WRITES (ieșire un șir), WRITEN , WRITED , WRITEOCT , WRITEHEX (ieșire numere în diverse forme), un singur apel a fost folosit în care era posibil să se intercaleze „doar text” cu valorile de ieșire.
Limbajul Bee care l-a urmat în 1969 folosea deja numele printf cu un șir de format simplu (asemănător cu BCPL ), specificând doar unul dintre cele trei tipuri posibile și două reprezentări numerice: zecimal ( ), octal ( ), șiruri ( ) și caractere ( ), iar singura modalitate de a formata ieșirea în aceste funcții a fost să adăugați caractere înainte și după ieșirea valorii variabilei. [2]%d%o%s%c
De la introducerea primei versiuni a limbajului C ( 1970 ), familia printf a devenit principalul instrument de ieșire în format. Costul analizării șirului de format cu fiecare apel de funcție a fost considerat acceptabil, iar apelurile alternative pentru fiecare tip separat nu au fost introduse în bibliotecă. Specificaţia funcţiei a fost inclusă în ambele standarde lingvistice existente , publicate în 1990 şi 1999 . Specificația din 1999 conține câteva inovații din specificația din 1990.
Limbajul C++ folosește biblioteca standard C (conform standardului din 1990), inclusiv întreaga familie printf .
Ca alternativă, biblioteca standard C++ oferă un set de clase de intrare și ieșire a fluxului. Declarațiile de ieșire ale acestei biblioteci sunt sigure de tip și nu necesită analizarea șirurilor de format de fiecare dată când sunt apelate. Cu toate acestea, mulți programatori continuă să folosească familia printf , deoarece secvența de ieșire cu ei este de obicei mai compactă, iar esența formatului utilizat este mai clară.
Objective-C este un add-on destul de „subțire” pentru C, iar programele de pe acesta pot folosi direct funcțiile familiei printf .
În plus față de C și derivatele sale (C++, Objective-C), multe alte limbaje de programare folosesc sintaxa șirurilor de format tip printf:
În plus, datorită utilitarului printf inclus cu majoritatea sistemelor de tip UNIX, printf este folosit în multe scripturi shell (pentru sh , bash , csh , zsh , etc.).
Unele limbi și medii de programare mai recente folosesc, de asemenea, conceptul de ieșire bazată pe șiruri de format, dar cu o sintaxă diferită.
De exemplu, .Net Core Class Library (FCL) are o familie de metode System.String.Format , System.Console.Write și System.Console.WriteLine , dintre care unele supraîncărcări își produc datele conform unui șir de format. Deoarece informații complete despre tipurile de obiecte sunt disponibile în runtime .Net, nu este nevoie să treceți aceste informații în șirul de format.
Toate funcțiile au stem printf în numele lor . Prefixele dinaintea numelui funcției înseamnă:
Toate funcțiile iau un șir de format ca unul dintre parametrii ( format ) (descrierea sintaxei șirului de mai jos). Returnează numărul de caractere scrise (tipărite), fără a include caracterul nul de la sfârșitul lui . Numărul de argumente care conțin date pentru ieșirea formatată trebuie să fie cel puțin atât cât este menționat în șirul de format. Argumentele „extra” sunt ignorate.
Funcțiile familiei n ( snprintf , vsnprintf ) returnează numărul de caractere care ar fi tipărit dacă parametrul n (limitarea numărului de caractere de tipărit) ar fi suficient de mare. În cazul codificărilor pe un singur octet , valoarea returnată corespunde lungimii dorite a șirului (fără a include caracterul nul de la sfârșit).
Funcțiile familiei s ( sprintf , snprintf , vsprintf , vsnprintf ) iau ca prim(e) parametru( i ) un pointer către zona de memorie unde va fi scris șirul rezultat. Funcțiile care nu au o limită a numărului de caractere scrise sunt funcții nesigure , deoarece pot duce la o eroare de depășire a tamponului dacă șirul de ieșire este mai mare decât dimensiunea memoriei alocate pentru ieșire.
Funcțiile familiei f scriu un șir în orice flux deschis ( parametrul fluxului ), în special în fluxurile de ieșire standard ( stdout , stderr ). fprintf(stdout, format, …)echivalent cu printf(format, …).
Funcțiile familiei v preiau argumente nu ca un număr variabil de argumente (ca toate celelalte funcții printf), ci ca o listă va list . În acest caz, când funcția este apelată, macro -ul va end nu este executat.
Funcțiile familiei w (primul caracter) sunt o implementare limitată Microsoft a familiei de funcții s : wsprintf , wnsprintf , wvsprintf , wvnsprintf . Aceste funcții sunt implementate în bibliotecile dinamice user32.dll și shlwapi.dll ( n funcții). Nu acceptă ieșirea în virgulă mobilă, iar wnsprintf și wvnsprintf acceptă doar text aliniat la stânga.
Funcțiile familiei w ( wprintf , swprintf ) implementează suport pentru codificări pe mai mulți octeți, toate funcțiile acestei familii funcționează cu pointeri către șiruri de mai mulți octeți ( wchar_t ).
Funcțiile familiei a ( asprintf , vasprintf ) alocă memorie pentru șirul de ieșire folosind funcția malloc , memoria este eliberată în procedura de apelare, în cazul unei erori la executarea funcției, memoria nu este alocată.
Valoare returnată: valoare negativă — semn de eroare; dacă reușesc, funcțiile returnează numărul de octeți scriși/ieșiți (ignorând octetul nul la sfârșit), funcția snprintf tipărește numărul de octeți care ar fi scrisi dacă n ar fi suficient de mare.
La apelarea snprintf , n poate fi zero (caz în care s poate fi un pointer nul ), caz în care nu se face nicio scriere, funcția returnează doar valoarea returnată corectă.
În C și C++, un șir de format este un șir terminat cu nul. Toate caracterele, cu excepția specificatorilor de format, sunt copiate neschimbate în șirul rezultat. Semnul standard al începutului specificatorului de format este caracterul %( semnul de procente ), pentru a afișa semnul în sine %, se folosește dublarea acestuia %%.
Specificatorul de format arată astfel:
% [ steaguri ][ lățime ][ . precizie ][ dimensiune ] tipComponentele necesare sunt caracterul de început al specificatorului de format ( %) și tipul .
SteaguriSemn | Numele semnului | Sens | În lipsa acestui semn | Notă |
---|---|---|---|---|
- | minus | valoarea de ieșire este aliniată la stânga în cadrul lățimii minime a câmpului | pe dreapta | |
+ | un plus | specificați întotdeauna un semn (plus sau minus) pentru valoarea numerică zecimală afișată | numai pentru numere negative | |
spaţiu | puneți un spațiu înaintea rezultatului dacă primul caracter al valorii nu este un semn | Ieșirea poate începe cu un număr. | Caracterul + are prioritate față de caracterul spațiu. Folosit numai pentru valori zecimale cu semn. | |
# | zăbrele | „forma alternativă” de ieșire a valorii | La ieșirea numerelor în format hexazecimal sau octal, numărul va fi precedat de o caracteristică de format (0x sau 0, respectiv). | |
0 | zero | introduceți câmpul la lățimea specificată în câmpul de lățime a secvenței de evacuare cu simbolul0 | tampon cu spații | Folosit pentru tipurile d , i , o , u , x , X , a , A , e , E , f , F , g , G . Pentru tipurile d , i , o , u , x , X , dacă este specificată precizia , acest indicator este ignorat. Pentru alte tipuri, comportamentul este nedefinit.
Dacă este specificat un semnalizator minus „-”, acesta este de asemenea ignorat. |
Lățimea (caracter zecimal sau asterisc ) specifică lățimea minimă a câmpului (inclusiv semnul pentru numere). Dacă reprezentarea valorii este mai mare decât lățimea câmpului, atunci intrarea este în afara câmpului (de exemplu, %2i pentru o valoare de 100 va oferi o valoare a câmpului de trei caractere), dacă reprezentarea valorii este mai mică decât numărul specificat, apoi va fi captusit (implicit) cu spatii in stanga, comportamentul poate varia in functie de alte steaguri setate. Dacă este specificat un asterisc ca lățime, lățimea câmpului este specificată în lista de argumente înainte de valoarea de ieșire (de exemplu, printf( "%0*x", 8, 15 );va afișa textul 0000000f). Dacă un modificator de lățime negativ este specificat în acest fel, indicatorul - este considerat setat , iar valoarea modificatorului de lățime este setată la absolut.
Modificator de preciziePrecizia este specificată ca o perioadă urmată de un număr zecimal sau de un asterisc ( * ), dacă nu există niciun număr sau asterisc (este prezent doar un punct), atunci numărul se presupune a fi zero. Un punct este folosit pentru a indica precizia, chiar dacă este afișată o virgulă la ieșirea numerelor în virgulă mobilă.
Dacă după punct este specificat un caracter asterisc, atunci când se procesează șirul de format, valoarea câmpului este citită din lista de argumente. (În același timp, dacă caracterul asterisc este atât în câmpul de lățime, cât și în câmpul de precizie, este indicată mai întâi lățimea, apoi precizia și abia apoi valoarea pentru ieșire). De exemplu, printf( "%0*.*f", 8, 4, 2.5 );va afișa textul 002.5000. Dacă un modificator de precizie negativ este specificat în acest fel, atunci nu există nici un modificator de precizie. [19]
Modificator de dimensiuneCâmpul dimensiune vă permite să specificați dimensiunea datelor transmise funcției. Necesitatea acestui câmp se explică prin particularitățile transmiterii unui număr arbitrar de parametri unei funcții în limbajul C: funcția nu poate determina „independent” tipul și dimensiunea datelor transferate, deci informații despre tipul parametrilor și ale acestora dimensiunea exactă trebuie transmisă în mod explicit.
Având în vedere influența specificațiilor de dimensiune asupra formatării datelor întregi, trebuie remarcat faptul că în limbajele C și C++ există un lanț de perechi de tipuri de numere întregi semnate și nesemnate, care, în ordinea nedescrescătoare a dimensiunilor, sunt dispuse astfel:
tip semnat | Tip nesemnat |
---|---|
semnat char | nesemnat char |
semnat scurt ( scurt ) | unsigned short int ( unsigned short ) |
semnat int ( int ) | unsigned int ( nesemnat ) |
semnat long int ( lung ) | unsigned long int ( unsigned long ) |
semnat long long int ( long long ) | unsigned long long int ( unsigned long long ) |
Dimensiunile exacte ale tipurilor sunt necunoscute, cu excepția tipurilor cu semn și caracter nesemnat .
Tipurile asociate semnate și nesemnate au aceeași dimensiune, iar valorile reprezentabile în ambele tipuri au aceeași reprezentare în ele.
Tipul char are aceeași dimensiune ca și tipurile char semnat și nesemnat și partajează un set de valori reprezentabile cu unul dintre acele tipuri. În plus, se presupune că char este un alt nume pentru unul dintre aceste tipuri; o astfel de presupunere este acceptabilă pentru prezenta considerație.
În plus, C are tipul _Bool , în timp ce C++ are tipul bool .
Când se transmit argumente unei funcții care nu corespund parametrilor formali din prototipul funcției (care sunt toate argumente care conțin valori de ieșire), aceste argumente sunt supuse promoțiilor standard , și anume:
Astfel, funcțiile printf nu pot lua argumente de tip float , _Bool sau bool , sau tipuri întregi mai mici decât int sau unsigned .
Setul de specificatori de dimensiune utilizat depinde de specificatorul de tip (vezi mai jos).
specificatorul | %d, %i, %o, %u, %x,%X | %n | Notă |
---|---|---|---|
dispărut | int sau unsigned int | pointer către int | |
l | long int sau unsigned long int | pointer la long int | |
hh | Argumentul este de tip int sau unsigned int , dar este forțat să tastați signed char sau unsigned char , respectiv | pointer către caracterul semnat | există oficial în C începând cu standardul din 1999 și în C++ începând cu standardul din 2011. |
h | Argumentul este de tip int sau unsigned int , dar este forțat să tastați short int sau unsigned short int , respectiv | pointer la scurt int | |
ll | long long int sau unsigned long long int | pointer la long long int | |
j | intmax_t sau uintmax_t | pointer către intmax_t | |
z | size_t (sau tip semnat echivalent cu dimensiunea) | pointer către un tip cu semn echivalent ca dimensiune cu size_t | |
t | ptrdiff_t (sau un tip echivalent nesemnat) | pointer către ptrdiff_t | |
L | __int64 sau nesemnat __int64 | pointer către __int64 | Pentru Borland Builder 6 (specificatorul llse așteaptă la un număr de 32 de biți) |
Specificațiile hși hhsunt utilizate pentru a compensa promoțiile de tip standard împreună cu tranzițiile de la tipurile semnate la cele nesemnate sau invers.
De exemplu, luați în considerare o implementare C în care tipul char este semnat și are o dimensiune de 8 biți, tipul int are o dimensiune de 32 de biți și este utilizată o modalitate suplimentară de codificare a numerelor întregi negative.
char c = 255 ; printf ( "%X" , c );Un astfel de apel va produce ieșire FFFFFFFF, care poate să nu fie ceea ce se aștepta programatorul. Într-adevăr, valoarea lui c este (char)(-1) , iar după promovarea tipului este -1 . Aplicarea formatului %Xface ca valoarea dată să fie interpretată ca nesemnată, adică 0xFFFFFFFF .
char c = 255 ; printf ( "%X" , ( caracter nesemnat ) c ); char c = 255 ; printf ( "%hhX" , c );Aceste două apeluri au același efect și produc rezultatul FF. Prima opțiune vă permite să evitați înmulțirea semnului atunci când promovați tipul, a doua o compensează deja „în interiorul” funcției printf .
specificatorul | %a, %A, %e, %E, %f, %F, %g,%G |
---|---|
dispărut | dubla |
L | dublu lung |
specificatorul | %c | %s |
---|---|---|
dispărut | Argumentul este de tip int sau unsigned int , dar este forțat să tastați char | char* |
l | Argumentul este de tip wint_t , dar este forțat să tastați wchar_t | wchar_t* |
Tipul indică nu numai tipul valorii (din punctul de vedere al limbajului de programare C), ci și reprezentarea specifică a valorii de ieșire (de exemplu, numerele pot fi afișate în formă zecimală sau hexazecimală). Scris ca un singur personaj. Spre deosebire de alte câmpuri, este obligatoriu. Dimensiunea maximă de ieșire acceptată dintr-o singură secvență de evadare este, conform standardelor, de cel puțin 4095 de caractere; în practică, majoritatea compilatorilor acceptă cantități substanțial mai mari de date.
Valori de tip:
În funcție de localitatea curentă , atât o virgulă, cât și un punct (și posibil un alt simbol) pot fi utilizate atunci când se afișează numere în virgulă mobilă. Comportamentul printf în raport cu caracterul care separă partea fracționară și întreagă a numărului este determinat de localitatea utilizată (mai precis, variabila LC NUMERIC ). [douăzeci]
Macrocomenzi speciale pentru un set extins de aliasuri de tip de date întregiAl doilea standard C (1999) oferă un set extins de alias-uri pentru tipurile de date întregi int N _t , uint N _t , int_least N _t , uint_least N _t , int_fast N _t , uint_fast N _t (unde N este adâncimea de biți necesară), intptr_t , uintptr_t , intmax_t , uintmax_t .
Fiecare dintre aceste tipuri se potrivește sau nu cu oricare dintre tipurile de numere întregi standard încorporate. Din punct de vedere formal, atunci când scrie cod portabil, programatorul nu știe dinainte ce specificație standard sau extinsă de dimensiune ar trebui să aplice.
int64_t x = 100000000000 ; int lățime = 20 ; printf ( "%0*lli" , lățime , x ); Greșit, deoarece int64_t poate să nu fie același cu long long int .Pentru a putea deduce valorile obiectelor sau expresiilor de aceste tipuri într-un mod portabil și convenabil, implementarea definește pentru fiecare dintre aceste tipuri un set de macro-uri ale căror valori sunt șiruri care combină specificații de dimensiune și tip.
Numele macrocomenzilor sunt după cum urmează:
O pereche de tipuri semnate și nesemnate | Nume macro |
---|---|
int N_t și uint N_t _ _ | PRITN |
int_least N _t și uint_least N _t | PRITLEASTN |
int_fastN_t și uint_fastN_t _ _ _ _ | PRITFASTN |
intmax_t și uintmax_t | PRITMAX |
intptr_t și uintptr_t | PRITPTR |
Iată T , una dintre următoarele specificații de tip: d, i, u, o, x, X.
int64_t x = 100000000000 ; int lățime = 20 ; printf ( "%0*" PRIi64 , lățime , x ); Modul corect de a scoate o valoare de tip int64_t în limbajul C.Este posibil să observați că tipurile intmax_t și uintmax_t au un specificator de dimensiune standard j, astfel încât macro-ul este cel mai probabil întotdeauna definit ca . PRITMAX"jT"
Conform standardului Single UNIX (practic echivalent cu standardul POSIX ), următoarele completări la printf sunt definite în raport cu ISO C, sub extensia XSI (X/Open System Interface):
Biblioteca GNU C ( libc ) adaugă următoarele extensii:
GNU libc acceptă înregistrarea tipului personalizat, permițând programatorului să definească formatul de ieșire pentru propriile structuri de date. Pentru a înregistra un nou tip , utilizați funcția
int register_printf_function (int type, printf_function handler-function, printf_arginfo_function arginfo-function), unde:
Pe lângă definirea de noi tipuri, înregistrarea permite redefinirea tipurilor existente (cum ar fi s , i ).
Microsoft Visual CMicrosoft Visual Studio pentru limbajele de programare C/C++ în formatul specificației printf (și alte funcții de familie) oferă următoarele extensii:
valoarea câmpului | tip de |
---|---|
I32 | semnat __int32 , nesemnat __int32 |
I64 | semnat __int64 , nesemnat __int64 |
eu | ptrdiff_t , dimensiunea_t |
w | echivalent cu l pentru șiruri și caractere |
Mediul de matematică Maple are și o funcție printf care are următoarele caracteristici:
FormatareExemplu:
> printf("%a =%A", `+`, `+`); `+` = + > printf("%a =%m", `+`, `+`); `+` = I"+f*6"F$6#%(buildingGF$"$Q"F$F$F$F"%*protectedG ConcluzieFuncția fprintf a lui Maple ia fie un descriptor de fișier (returnat de fopen), fie un nume de fișier ca prim argument. În acest din urmă caz, numele trebuie să fie de tip „simbol”, dacă numele fișierului conține puncte, atunci trebuie să fie închis în backticks sau convertit cu funcția convert (nume_fișier, simbol).
Funcțiile familiei printf iau o listă de argumente și dimensiunea lor ca parametru separat (în șirul de format). O nepotrivire între șirul de format și argumentele transmise poate duce la un comportament imprevizibil, coruperea stivei, executarea codului arbitrar și distrugerea zonelor de memorie dinamică. Multe funcții ale familiei sunt numite „unsafe” ( în engleză unsafe ), deoarece nici măcar nu au capacitatea teoretică de a se proteja împotriva datelor incorecte.
De asemenea, funcțiile din familia s (fără n , precum sprintf , vsprintf ) nu au limite în ceea ce privește dimensiunea maximă a șirului scris și pot duce la o eroare de depășire a memoriei tampon (când datele sunt scrise în afara zonei de memorie alocată).
Ca parte a convenției de apelare cdecl , curățarea stivei este realizată de funcția de apelare. Când printf este apelat , argumentele (sau pointerii către ele) sunt plasate în ordinea în care sunt scrise (de la stânga la dreapta). Pe măsură ce șirul de format este procesat, funcția printf citește argumente din stivă. Sunt posibile următoarele situații:
Specificațiile limbajului C descriu doar două situații (funcționare normală și argumente suplimentare). Toate celelalte situații sunt eronate și duc la un comportament nedefinit al programului (în realitate, ducând la rezultate arbitrare, până la execuția unor secțiuni de cod neplanificate).
Prea multe argumenteCând trece un număr excesiv de argumente, funcția printf citește argumentele necesare pentru a procesa corect șirul de format și revine la funcția de apelare. Funcția de apelare, în conformitate cu specificația, șterge stiva de parametrii trecuți funcției apelate. În acest caz, parametrii suplimentari pur și simplu nu sunt utilizați, iar programul continuă fără modificări.
Nu sunt suficiente argumenteDacă există mai puține argumente pe stivă la apelarea printf decât este necesar pentru a procesa șirul de format, atunci argumentele lipsă sunt citite din stivă, în ciuda faptului că există date arbitrare pe stivă (nu sunt relevante pentru munca printf ) . Dacă prelucrarea datelor a fost „reușită” (adică nu a terminat programul, nu a blocat sau nu a scris în stivă), după revenirea la funcția de apelare, valoarea indicatorului de stivă este returnată la valoarea inițială și programul continuă.
La procesarea valorilor stivei „extra”, sunt posibile următoarele situații:
Formal, orice discrepanță între tipul de argument și așteptare cauzează un comportament nedefinit al programului. În practică, există mai multe cazuri care sunt deosebit de interesante din punctul de vedere al practicii de programare:
Alte cazuri, de regulă, duc la un comportament evident incorect și sunt ușor de detectat.
Nu se potrivește dimensiunea argumentului întreg sau în virgulă mobilăPentru un argument întreg (cu o specificație de format întreg), sunt posibile următoarele situații:
Pentru un argument real (cu o specificație de format reală), pentru orice nepotrivire de dimensiune, valoarea de ieșire, de regulă, nu se potrivește cu valoarea transmisă.
De regulă, dacă dimensiunea oricărui argument este greșită, procesarea corectă a tuturor argumentelor ulterioare devine imposibilă, deoarece este introdusă o eroare în indicatorul la argumente. Cu toate acestea, acest efect poate fi compensat prin alinierea valorilor pe stivă.
Alinierea valorilor pe stivăMulte platforme au reguli de aliniere a valorilor întregi și/sau reale care necesită (sau recomandă) ca acestea să fie plasate la adrese care sunt multipli de dimensiunea lor. Aceste reguli se aplică și pentru transmiterea argumentelor funcției pe stivă. În acest caz, o serie de nepotriviri între tipurile de parametri așteptați și reali pot trece neobservate, creând iluzia unui program corect.
uint32_t a = 1 ; uint64_t b = 2 , c = 3 ; printf ( "%" PRId64 "%" PRId64 "%" PRId64 , b , a , c ); În acest exemplu, parametrul atip real are uint32_to specificație de format nevalidă asociată %"PRId64"cu tipul uint64_t. Cu toate acestea, pe unele platforme cu un tip de 32 de biți int, în funcție de ordinea de octeți acceptată și de direcția de creștere a stivei, eroarea poate trece neobservată. Parametrii actuali bși cvor fi aliniați la o adresă care este un multiplu al mărimii lor (de două ori dimensiunea a). Și „între” valori a, bva rămâne un spațiu gol (de obicei zero) de 32 de biți în dimensiune; când BOM este procesată, valoarea de %"PRId64"32 de biți a, împreună cu acest spațiu alb, vor fi interpretate ca o singură valoare de 64 de biți.O astfel de eroare poate apărea în mod neașteptat la portarea codului programului pe o altă platformă, schimbarea compilatorului sau modul de compilare.
Discrepanță potențială de dimensiuneDefinițiile limbajelor C și C++ descriu doar cerințele cele mai generale pentru dimensiunea și reprezentarea tipurilor de date. Prin urmare, pe multe platforme, reprezentarea unor tipuri de date formal diferite se dovedește a fi aceeași. Acest lucru face ca unele nepotriviri de tip să rămână nedetectate pentru o lungă perioadă de timp.
De exemplu, pe platforma Win32, se acceptă în general că dimensiunile tipurilor intși long intsunt aceleași (32 de biți). Astfel, apelul printf("%ld", 1)sau printf("%d", 1L)va fi executat „corect”.
O astfel de eroare poate apărea în mod neașteptat la portarea codului programului pe o altă platformă, schimbarea compilatorului sau modul de compilare.
Când scrieți programe în limbajul C++, trebuie să aveți grijă să obțineți valorile variabilelor declarate folosind aliasuri de tip întreg, în special size_tși ptrdiff_t; definiția formală a bibliotecii standard C++ se referă la primul standard C (1990). Al doilea standard C (1999) definește specificatorii de dimensiune pentru tipuri size_tși și pentru un număr de alte tipuri pentru utilizare cu obiecte similare. ptrdiff_tMulte implementări C++ le suportă și ele.
dimensiunea_t s = 1 ; printf ( "%u" , s ); Acest exemplu conține o eroare care poate apărea pe platformele sizeof (unsigned int)în care sizeof (size_t). dimensiunea_t s = 1 ; printf ( "%zu" , s ); Modul corect de a deduce valoarea unui obiect tip este size_tîn limbajul C. Tip nepotrivire atunci când dimensiunea corespundeDacă argumentele transmise au aceeași dimensiune, dar au un tip diferit, atunci programul va rula adesea „aproape corect” (nu va cauza erori de acces la memorie), deși valoarea de ieșire este probabil să fie lipsită de sens. Trebuie remarcat faptul că amestecarea tipurilor întregi pereche (semnate și nesemnate) este permisă, nu provoacă un comportament nedefinit și uneori este folosită în mod deliberat în practică.
Când se utilizează o specificație de format %s, o valoare de argument a unui tip întreg, real sau de tip pointer, altul decât char*, va fi interpretată ca adresa unui șir. Această adresă, în general, poate indica în mod arbitrar o zonă de memorie inexistentă sau inaccesibilă, ceea ce va duce la o eroare de acces la memorie, sau către o zonă de memorie care nu conține o linie, ceea ce va duce la ieșire aiurea, eventual foarte mare. .
Deoarece printf (și alte funcții ale familiei) poate scoate textul șirului de format fără modificări, dacă nu conține secvențe de escape, atunci este posibilă ieșirea textului de către comandă
printf(text_to_print);
Dacă text_to_print este obținut din surse externe (citește dintr-un fișier , primit de la utilizator sau de la sistemul de operare), atunci prezența unui semn de procente în șirul rezultat poate duce la consecințe extrem de nedorite (până la înghețarea programului).
Exemplu de cod incorect:
printf(" Current status: 99% stored.");
acest exemplu conține o secvență de evadare „% s” care conține caracterul secvenței de evadare (%), un steag (spațiu) și un tip de date șir ( s ). Funcția, după ce a primit secvența de control, va încerca să citească indicatorul către șirul din stivă. Deoarece nu au fost transferați parametri suplimentari funcției, valoarea care trebuie citită din stivă este nedefinită. Valoarea rezultată va fi interpretată ca un pointer către un șir terminat cu nul. Ieșirea unui astfel de „șir” poate duce la o descărcare arbitrară a memoriei, o eroare de acces la memorie și o corupție a stivei. Acest tip de vulnerabilitate se numește atac de șir de format . [21]
Funcția printf , atunci când scoate un rezultat, nu este limitată de numărul maxim de caractere de ieșire. Dacă, ca urmare a unei erori sau o neglijență, sunt afișate mai multe caractere decât se aștepta, cel mai rău lucru care se poate întâmpla este „distrugerea” imaginii de pe ecran. Creată prin analogie cu printf , funcția sprintf nu a fost, de asemenea, limitată în dimensiunea maximă a șirului rezultat. Totuși, spre deosebire de terminalul „infinit”, memoria pe care o alocă aplicația pentru șirul rezultat este întotdeauna limitată. Iar în cazul depășirii limitelor așteptate, înregistrarea se face în zone de memorie aparținând altor structuri de date (sau, în general, în zone de memorie inaccesibile, ceea ce înseamnă că programul se blochează pe aproape toate platformele). Scrierea în zone arbitrare ale memoriei duce la efecte imprevizibile (care pot apărea mult mai târziu și nu sub forma unei erori de program, ci sub forma corupției datelor utilizatorului). Lipsa unei limite privind dimensiunea maximă a șirului este o eroare fundamentală de planificare atunci când se dezvoltă o funcție. Din această cauză funcțiile sprintf și vsprintf au starea nesigură . În schimb, a dezvoltat funcțiile snprintf , vsnprintf , care iau un argument suplimentar care limitează șirul maxim rezultat. Funcția swprintf , care a apărut mult mai târziu (pentru lucrul cu codificări pe mai mulți octeți), ia în considerare acest neajuns și ia un argument pentru a limita șirul rezultat. (De aceea nu există nicio funcție snwprintf ).
Un exemplu de apel periculos la sprintf :
charbuffer[65536]; char* nume = get_user_name_from_keyboard(); sprintf(buffer, „Nume utilizator:%s”, nume);Codul de mai sus presupune implicit că utilizatorul nu va tasta 65 de mii de caractere pe tastatură, iar tamponul „ar trebui să fie suficient”. Dar utilizatorul poate redirecționa intrarea dintr-un alt program sau poate introduce mai mult de 65.000 de caractere. În acest caz, zonele de memorie vor fi corupte și comportamentul programului va deveni imprevizibil.
Funcțiile familiei printf folosesc tipuri de date C. Dimensiunile acestor tipuri și raporturile lor pot varia de la platformă la platformă. De exemplu, pe platformele pe 64 de biți, în funcție de modelul ales ( LP64 , LLP64 sau ILP64 ), dimensiunile tipurilor int și long pot diferi. Dacă programatorul setează șirul de format la „aproape corect”, codul va funcționa pe o platformă și va da rezultat greșit pe alta (în unele cazuri, poate duce la coruperea datelor).
De exemplu, codul printf( "text address: 0x%X", "text line" );funcționează corect pe o platformă de 32 de biți ( dimensiunea ptrdiff_t și dimensiunea int 32 de biți) și pe un model IPL64 de 64 de biți (unde dimensiunile ptrdiff_t și int sunt de 64 de biți), dar va da un rezultat incorect pe un model de 64 de biți. -platforma de biți a unui model LP64 sau LLP64, unde dimensiunea lui ptrdiff_t este de 64 de biți și dimensiunea lui int este de 32 de biți. [22]
În Oracle Java , tipurile înfășurate cu identificare dinamicăprintf sunt utilizate în analogul unei funcții , [6] în Embarcadero Delphi - un strat intermediar , [23] în diverse implementări în C++ [24] - supraîncărcare a operațiunilor , în C + + 20 - șabloane variabile. În plus, formatele ( , etc.) nu specifică tipul argumentului, ci doar formatul de ieșire, astfel încât schimbarea tipului argumentului poate provoca o urgență sau poate întrerupe logica de nivel înalt (de exemplu, „întrerupe” dispunerea tabelului) - dar nu strica memoria. array of const%d%f
Problema este agravată de standardizarea insuficientă a șirurilor de format în diferite compilatoare: de exemplu, versiunile timpurii ale bibliotecilor Microsoft nu au acceptat "%lld"(a trebuit să specificați "%I64d"). Există încă o divizare între Microsoft și GNU după tip size_t: %Iuprimul și %zucel din urmă. GNU C nu necesită o swprintflungime maximă de șir într-o funcție (trebuie să scrieți snwprintf).
Funcțiile familiei printfsunt convenabile pentru localizarea software-ului : de exemplu, este mai ușor de tradus decât fragmentele de «You hit %s instead of %s.»șir și . Dar și aici există o problemă: este imposibil să rearanjați șirurile înlocuite în locuri pentru a obține: . «You hit »« instead of »«.»«Вы попали не в <2>, а в <1>.»
Extensiile printffolosite în Oracle Java și Embarcadero Delphi vă permit încă să rearanjați argumentele.
În standardul POSIX , este descris utilitarul printf , care formatează argumentele conform modelului corespunzător, similar cu funcția printf .
Utilitarul are următorul format de apel: , unde printf format [argument …]
Comenzi Unix | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
|