פורמט מחרוזת printf

מתוך ויקיפדיה, האנציקלופדיה החופשית
דוגמה לפונקציית printf

פורמט מחרוזת printf היא פרמטר בקרה המשמש מחלקה של פונקציות בספריות הקלט/פלט של C ושפות תכנות רבות אחרות. המחרוזת כתובה בשפת תבנית פשוטה: תווים בדרך כלל מועתקים כמות שהם לתוך הפלט של הפונקציה, אך מפרטי פורמט, שמתחילים בתו %, מציינים את המיקום והשיטה לתרגום נתון (כגון מספר) לתווים.

"printf" הוא השם של אחת מפונקציות הפלט העיקריות של C, והוא קיצור של "print formatted". מחרוזות בפורמט printf משלימות מחרוזות בפורמט scanf, המספקות קלט מעוצב. בשני המקרים הפונקציות מספקות פונקציונליות פשוטה ופורמט קבוע בהשוואה למעבדי תבניות מתוחכמים וגמישים יותר או מנתחים, אך מספיקים למטרות רבות.

שפות רבות מלבד C מעתיקות את תחביר המחרוזת בפורמט printf, בקירוב או בדיוק, בפונקציות קלט/פלט משלהן.

חוסר התאמה בין מפרטי הפורמט וסוג הנתונים עלול לגרום לקריסות ולפגיעויות אחרות. מחרוזת הפורמט עצמה היא לעיתים קרובות מאוד מחרוזת מילולית, המאפשרת ניתוח סטטי של קריאת הפונקציה. עם זאת, זה יכול להיות גם ערך של משתנה, מה שמאפשר פורמט דינמי אך גם פגיעות אבטחה המכונה ניצול מחרוזת פורמט בלתי מבוקר .

היסטוריה[עריכת קוד מקור | עריכה]

שפות תכנות מוקדמות כמו Fortran השתמשו בהצהרות מיוחדות עם תחביר שונה לחלוטין מחישובים אחרים כדי לבנות תיאורי פורמט. בדוגמה זו, הפורמט מצוין בשורה 601, והפקודה WRITE מתייחסת אליו לפי מספר שורה:

 WRITE OUTPUT TAPE 6, 601, IA, IB, IC, AREA
 601 FORMAT (4H A= ,I5,5H B= ,I5,5H C= ,I5,
 & 8H AREA= ,F10.2, 13H SQUARE UNITS)

ל-ALGOL 68 היה ממשק API יותר דמוי פונקציה, אבל עדיין השתמש בתחביר מיוחד (מפרידי $ מקיפים תחביר פורמט מיוחד):

printf(($"Color "g", number1 "6d,", number2 "4zd,", hex "16r2d,", float "-d.2d,", unsigned value"-3d"."l$,
 "red", 123456, 89, BIN 255, 3.14, 250));

אבל השימוש בקריאות הפונקציות ובסוגי הנתונים הרגילים מפשט את השפה והקומפיילר, ומאפשר לכתוב את המימוש של הקלט/פלט באותה שפה. יתרונות אלו עולים על החסרונות (כגון חוסר מוחלט של בטיחות טיפוס במקרים רבים) וברוב השפות החדשות יותר קלט/פלט אינו חלק מהתחביר.

מקורו של printf של C בפונקציית writef של BCPL (1966). בהשוואה ל- C ו- printf, *N הוא רצף מילוט בשפת BCPL המייצג תו שורה חדשה (שעבורו C משתמש ברצף המילוט \n) והסדר של שדה הרוחב ושדה הטיפוס הפוכים ב-writef: [1]

WRITEF("%I2-QUEENS PROBLEM HAS %I5 SOLUTIONS*N", NUMQUEENS, COUNT)

ככל הנראה ההעתקה הראשונה של התחביר מחוץ לשפת C הייתה פקודת ה-Unix printf shell, שהופיעה לראשונה בגרסה 4, כחלק מההיסב ל-C.

מפרט פורמט שומרי המקום[עריכת קוד מקור | עריכה]

יצירת הפורמט מתבצעת באמצעות שומרי מקום בתוך מחרוזת הפורמט. לדוגמה, אם תוכנית אמורה להדפיס את גילו של אדם, היא יכולה להציג את הפלט על ידי הקדמת "הגיל שלך הוא ", ושימוש בתו המציין מספר עשרוני שלם - d כדי לציין שאנו רוצים שהמספר השלם של הגיל יוצג מיד לאחר הודעה זו, אנו עשויים להשתמש במחרוזת הפורמט:

printf("Your age is %d", age);

תחביר[עריכת קוד מקור | עריכה]

התחביר עבור שומר מקום של פורמט הוא

שדה פרמטר[עריכת קוד מקור | עריכה]

זוהי הרחבה של POSIX ולא כלולה בתקן C99 של שפת C. השימוש בשדה הוא על ידי הוספת פרמטר מספרי ולאחריו התו $ בין התו המיוחד % ובין התו שמסמן את הטיפוס, למשל %2$d מסמן שימוש בערך השני מבין הערכים שלאחר מחרוזת הבקרה. ניתן להשמיט את השדה, בתנאי שאף אחד משומרי המקום האחרים לא עושה בו שימוש.

תכונה זו בשימוש בעיקר בלוקליזציה, שבה סדר הופעתם של פרמטרים משתנה בהתאם לקונבנציה התלויה בשפה.

במערכת ההפעלה Microsoft Windows, התמיכה בתכונה זו ממומשת בפונקציה נפרדת, printf_p.

שדה דגלים[עריכת קוד מקור | עריכה]

שדה הדגלים יכול להיות אפס או יותר (בכל סדר) מהתווים הבאים:

  • התו מינוס (-) משמש ליישור לשמאל של הפלט של שומר המקום (ברירת המחדל היא ליישר לימין).
  • התו פלוס (+) משמש להוספת פלוס לפני מספרים חיוביים. ברירת המחדל היא להוסיף מינוס לפני מספרים שליליים ולהשאיר מספרים חיוביים ללא סימן מקדים.
  • התו רווח ( ) משמש להוספת רווח לפני מספרים חיוביים. ברירת המחדל היא לא להוסיף דבר לפני מספרים חיוביים. כאשר נעשה שימוש בתו זה יחד עם תו פלוס, תו פלוס גובר ויודפס פלוס לפני מספרים חיוביים.
  • התו אפס (0) משמש לריפוד מספרים באפסים מובילים לפני המספר, כאשר הוגדר רוחב לשדה. ברירת המחדל מרפדת ברווחים. לשם המחשה, printf("%4X",3) תייצר " 3" ואילו printf("%04X",3) תייצר "0003".
  • התו אפוסטרוף (') משמש להוספת תווים מפרידים לאחר שלוש ספרות (כגון 1,000 במקום 1000 שיודפס בברירת המחדל).
  • התו סולמית (#) משמש למטרות שונות בטיפוסי נתונים שונים. בטיפוסים המסומנים עם g ו-G, לא מוסרים אפסים לאחר הנקודה העשרונית. בטיפוסים המסומנים עם f, F, e, E, g, G הפלט תמיד יכיל נקודה עשרונית. עבור הטיפוסים המסומנים עם o, x, X הטקסט 0, 0x, 0X יופיע לפני מספרים שאינם אפס.

שדה רוחב[עריכת קוד מקור | עריכה]

שדה הרוחב מציין מספר מינימלי של תווים לפלט, והוא משמש בדרך כלל לרפד שדות ברוחב קבוע בפלט עבור טבלאות, שם העמודות יהיו צרות יותר אילולי הריפוד. השדה אינו גורם לקיצוץ של שדות גדולים מרוחב השדה שצוין.

ניתן להשמיט את שדה הרוחב, או לספק בו ערך מספרי שלם, או ערך דינמי אשר מועבר כארגומנט נוסף, כאשר מסומן באמצעות כוכבית. [2] לדוגמה, printf("%*d", 5, 10) יביא להדפסה של 10, ברוחב כולל של 5 תווים.

אף על פי שאינו חלק משדה הרוחב, אפס מוביל מתפרש כדגל ריפוד האפס שהוזכר לעיל, וערך שלילי מטופל כערך החיובי יחד עם היישור לשמאל שהוזכר גם כן למעלה.

שדה דיוק[עריכת קוד מקור | עריכה]

שדה הדיוק מציין בדרך כלל מגבלת מקסימום על הפלט, בהתאם לטיפוס המסוים. עבור טיפוסים מסוג נקודה צפה, הוא מציין את מספר הספרות מימין לנקודה העשרונית אליו יש לעגל. עבור טיפוס המחרוזת, הוא מגביל את מספר התווים שיש להדפיס, ולאחר מכן המחרוזת נחתכת.

ניתן להשמיט את שדה הדיוק, או להזין ערך מספרי שלם, או ערך דינמי אשר מועבר כארגומנט נוסף כאשר מסומן באמצעות כוכבית. לדוגמה, printf("%.*s", 3, "abcdef") יודפס כ- abc .

שדה אורך[עריכת קוד מקור | עריכה]

ניתן להשמיט את שדה האורך או להזין אחד מהערכים הבאים:

  • hh - לטיפוסים של מספרים שלמים, גורם ל-printf לצפות למספר מגודל int שקודם מתו char.
  • h - לטיפוסים של מספרים שלמים, גורם ל-printf לצפות למספר מגודל int שקודם מ-short.
  • l - לטיפוסים של מספרים שלמים, גורם ל-printf לצפות למספר מגודל long. לטיפוסים של מספרים בפורמט נקודה צפה, השדה לא משנה דבר.
  • ll - לטיפוסים של מספרים שלמים, גורם ל-printf לצפות למספר מגודל long long.
  • L - לטיפוסים של נקודה צפה, גורם ל-printf לצפות למספר מגודל long double.
  • z - לטיפוסים של מספרים שלמים, גורם ל-printf לצפות למספר מגודל size_t.
  • j - לטיפוסים של מספרים שלמים, גורם ל-printf לצפות למספר מגודל intmax-t.
  • t - לטיפוסים של מספרים שלמים, גורם ל-printf לצפות למספר מגודל ptrdiff_t.

בנוסף, התקיימו מספר אפשרויות אורך ספציפיות לפלטפורמה לפני השימוש הנרחב בתוספי ISO C99:

  • I - לטיפוסים של מספרים שלמים עם סימן, גורם ל-printf לצפות למספר מגודל ptrdiff_t. לטיפוסים של מספרים שלמים ללא סימן (כלומר, חיוביים בלבד) גורם ל-printf לצפות למספר מגודל size_t. נפוץ במערכות הפעלה חלונות (מבוססי 32 או 64 סיביות).
  • I32 - לטיפוסים של מספרים שלמים, גורם ל-printf לצפות למספר מגודל 32 ביט. נפוץ במערכות הפעלה חלונות (מבוססי 32 או 64 סיביות).
  • I64 - לטיפוסים של מספרים שלמים, גורם ל-printf לצפות למספר מגודל 64 ביט. נפוץ במערכות הפעלה חלונות (מבוססי 32 או 64 סיביות).
  • q - לטיפוסים של מספרים שלמים, גורם ל-printf לצפות למספר מגודל 64 ביט. נפוץ במערכות הפעלה מסוג BSD.

בתקן ISO C99 נכלל קובץ הדר inttypes.h שכולל מספר מאקרואים לשימוש ב-printf באופן שאינו תלוי במערכת ההפעלה.

שדה טיפוס[עריכת קוד מקור | עריכה]

שדה הטיפוס יכול להיות אחד מהבאים:

  • % - מדפיס את התו %. לא יכול לקבל שדות.
  • d, i - מספר עשרוני שלם. לתווים התנהגות זהה במסגרת הדפסה, רק בקריאת קלט עם scanf יש ביניהם הבדל.
  • u - מדפיס מספר עשרוני שלם ללא סימן (כלומר, חיובי).
  • f, F - מספר עם נקודה עשרונית בכתיבה רגילה (נקודה קבועה). הרישיות משפיעה רק על הרישיות של תוצאות שנכתבות כמחרוזת (inf, infinity ו-nan עבור f; ואילו עבור F:‏ INF, INFINITY ו-NAN).
  • e, E - מספרים עם נקודה עשרונית בפורמט נקודה צפה (d.ddddd). כאשר משתמשים ב-E יופיע בפלט E במקום e לייצוג האקספוננט. האקספוננט תמיד מכיל לפחות שתי ספרות. אם הערך הוא 0 האקספוננט הוא 00. בחלונות, ברירת המחדל היא 3 ספרות לאקספוננט, לדוגמה 1.5e002, אך ניתן לשנות זאת באמצעות הפונקציה _set_output_format הייחודית לחלונות.
  • g, G - מספר עם נקודה עשרונית שמיוצג באחד הפורמטים, נקודה קבועה או נקודה צפה, בהתאם לגודל המספר. g משתמש באותיות קטנות ו-G משתמש באותיות גדולות. כאשר נעשה שימוש בנקודה קבועה, אפסים חסרי משמעות מימין לנקודה העשרונית מושמטים, זאת בשונה מפורמט נקודה קבועה רגיל. בנוסף, הנקודה העשרונית מושמטת במספרים שלמים.
  • x, X - מספר שלם ללא סימן בבסיס הקסדצימלי. x משתמש באותיות קטנות ו-X באותיות גדולות.
  • o - מספר שלם ללא סימן בבסיס אוקטלי.
  • s - מחרוזת המסתיימת בתו ה-Null.
  • c - תו בודד.
  • p - מצביע לטיפוס void בפורמט שתלוי במימוש של השפה.
  • a, A - מספר עם נקודה עשרונית בבסיס הקסדצימלי, עם קידומת 0x או 0X, כתלות בגודל האות.
  • n - לא מדפיס כלום, אבל שומר למשתנה את מספר התווים שהודפסו נכון לאותה נקודת זמן.

שומרי מקום בפורמט מותאם אישית[עריכת קוד מקור | עריכה]

יש כמה מימושים של פונקציות דמויות printf המאפשרות הרחבות למיני-שפה המבוססת על תו מילוט, ובכך מאפשרות למתכנת פונקציית פירמוט ספציפית עבור טיפוסים שאינם מובנים בשפה. אחד המוכרים ביותר הוא register_printf_function() של glibc (שהוצא משימוש). עם זאת, הוא משמש לעיתים רחוקות בשל העובדה שהוא מתנגש עם בדיקה סטטית של מחרוזות פורמט. מימוש נוסף הוא Vstr, המאפשר הוספת שמות פורמטים מרובי תווים.

יישומים מסוימים (כמו Apache HTTP Server) כוללים פונקציה משלהם דמוית printf, ומטמיעים בתוכו הרחבות. עם זאת, לכל אלה נוטות להיות אותן בעיות שיש ל- register_printf_function() .

פונקציית Linux kernel, ‏printk תומכת במספר דרכים להצגת מבני ליבה באמצעות המפרט הגנרי %p, על ידי הוספת תווי פורמט נוספים.[3] לדוגמה, %pI4 מדפיס כתובת IPv4 בצורה מנוקדת-עשרונית. זה מאפשר בדיקה סטטית של מחרוזת פורמט (של החלק %p) על חשבון תאימות מלאה עם printf רגיל.

רוב השפות שיש להן פונקציה דמוית printf עוקפות את היעדר תכונה זו על ידי שימוש בפורמט %s והמרת האובייקט לייצוג מחרוזת.

פגיעויות[עריכת קוד מקור | עריכה]

מפרטי המרה לא חוקיים[עריכת קוד מקור | עריכה]

אם יש מעט מדי ארגומנטים שסופקו לספק ערכים עבור כל מפרטי ההמרה במחרוזת התבנית, או אם הארגומנטים אינם מהסוגים הנכונים, התוצאות אינן מוגדרות, ועלולות להקריס את התוכנית. המימושים אינם עקביים לגבי האם שגיאות תחביר במחרוזת צורכות ארגומנט ואיזה סוג ארגומנט הם צורכים. ארגומנטים עודפים הם חסרי השפעה. במספר מקרים, ההתנהגות הלא מוגדרת הובילה לפרצות אבטחה של "מתקפת מחרוזת פורמט". ברוב מוסכמות הקריאה של C או C++ ניתן להעביר ארגומנטים על המחסנית, מה שאומר שבמקרה של מעט מדי ארגומנטים printf יקרא מעבר לסוף מסגרת המחסנית הנוכחית, ובכך יאפשר לתוקף לקרוא את המחסנית.

כמה מהדרים, כמו GNU Compiler Collection, יבדקו באופן סטטי את מחרוזות הפורמט של פונקציות דמויות printf ויזהירו על בעיות (בעת שימוש בדגלים -Wall או -Wformat). GCC גם יזהיר על פונקציות בסגנון printf המוגדרות על ידי המשתמש אם ה"פורמט" הלא סטנדרטי __attribute__ מוחל על הפונקציה.

רוחב שדה לעומת תוחמים מפורשים בפלט טבלאי[עריכת קוד מקור | עריכה]

שימוש רק ברוחב שדות כדי לספק טבלאות, כמו בפורמט כמו %8d%8d%8d עבור שלושה מספרים שלמים בשלוש עמודות של 8 תווים, לא יבטיח שהפרדת השדות תישמר אם יופיעו מספרים גדולים בנתונים:

1234567 1234567 1234567 
123     123     123     
123     12345678123     

אובדן הפרדת שדות יכול בקלות להוביל לפלט פגום. במערכות המעודדות שימוש בתוכניות כאבני בניין בסקריפטים, ניתן להעביר נתונים פגומים כאלה ולפגוע בעיבוד נוסף שלהם, בלי להתחשב בעובדה שהמתכנת המקורי ציפה שהפלט ייקרא רק על ידי עיניים אנושיות. ניתן למנוע בעיות אלה על ידי הכללת תוחמים מפורשים, אפילו רווחים, בכל פורמטי הפלט הטבלאי. שינוי פשוט של הדוגמה המסוכנת הנזכרת ל- %7d %7d %7d מונע ממספרים מעמודות שונות להתמזג בפלט, עקב הרווחים הכלולים במפורש:

1234567 1234567 1234567 
123     123     123     
123     12345678 123     

אסטרטגיות דומות מיושמות על נתוני מחרוזות.

כתיבת זיכרון[עריכת קוד מקור | עריכה]

למרות שעל פני השטח זוהי פונקציית פלט, printf מאפשרת כתיבה למיקום זיכרון שצוין על ידי ארגומנט דרך %n. פונקציונליות זו משמשת מדי פעם כחלק מהתקפות מחרוזות פורמט משוכללות יותר.

הפונקציונליות %n גם הופכת את printf בטעות לשלמה טיורינג אפילו עם קבוצה מעוצבת היטב של ארגומנטים. משחק של איקס עיגול כתוב במחרוזת הפורמט הוא מנצח של תחרות IOCCC ה-27.[4]

שפות תכנות עם printf[עריכת קוד מקור | עריכה]

שפות המשתמשות במחרוזות פורמט החורגות מהסגנון במאמר זה (כגון AMPL ו-Elixir), שפות שיורשות את היישום שלהן מה-JVM או סביבה אחרת (כגון Clojure ו- Scala ), ושפות שאין להן printf נייטיבי סטנדרטי אך יש להן ספריות חיצוניות המדמות התנהגות printf (כגון JavaScript) אינן כלולות ברשימה זו.

  • AWK [5]
  • C
  • D
  • F#
  • G ( LabVIEW )
  • GNU MathProg
  • גנו אוקטבה
  • Go
  • האסקל
  • J
  • Java (מאז גרסה 1.5) ושפות JVM
  • Julia (דרך הספרייה הסטנדרטית שלה Printf[6])
  • Lua (string.format)
  • Maple
  • MATLAB
  • Max (דרך אובייקט sprintf)
  • Mythryl
  • PARI/GP
  • פרל
  • PHP
  • Python (באמצעות אופרטור %) [7]
  • R
  • Raku (באמצעות printf, sprintf ו-fmt)
  • Red
  • Ruby
  • Tcl (באמצעות פקודת format)
  • Transact-SQL (דרך xp_sprintf)
  • Vala (באמצעות print() ו- FileStream.printf() )
  • פקודת השירות printf, לפעמים מובנית במעטפת, כמו עם כמה מימושים של ה- KornShell (ksh), Bourne again shell (bash), או Z shell (zsh). פקודות אלה בדרך כלל תומכות בתווי מילוט כמו בשפת C במחרוזת הפורמט.

ראו גם[עריכת קוד מקור | עריכה]

קישורים חיצוניים[עריכת קוד מקור | עריכה]

הערות שוליים[עריכת קוד מקור | עריכה]

  1. ^ "BCPL". cl.cam.ac.uk. נבדק ב-19 במרץ 2018. {{cite web}}: (עזרה)
  2. ^ "printf - C++ Reference". cplusplus.com. נבדק ב-2020-06-10.
  3. ^ "Linux kernel Documentation/printk-formats.txt". Git.kernel.org. נבדק ב-2014-03-17.
  4. ^ "Best of show - abuse of libc". Ioccc.org. נבדק ב-2022-05-05.
  5. ^ ""The Open Group Base Specifications Issue 7, 2018 edition", "POSIX awk", "Output Statements"". pubs.opengroup.org. נבדק ב-2022-05-29.
  6. ^ "Printf Standard Library". The Julia Language Manual. נבדק ב-22 בפברואר 2021. {{cite web}}: (עזרה)
  7. ^ The Python Standard Library, Python Software Foundation