Menu

Format specifiers ב-C: המדריך המלא ל-printf ו-scanf

כל ה-format specifiers של C במקום אחד: מה אומרים %d, %s, %c, %f, %p וכל השאר, מגדירי האורך l, ll, h ו-z, רוחב, דיוק ודגלים, ואילו מהם שונים בין printf ל-scanf.

בדף הזה יש עורכים שאפשר להריץ - לערוך, להריץ ולראות את הפלט מיד.

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

הצורה הכללית של מגדיר היא:

%[flags][width][.precision][length]conversion

רק ה-% ואות ההמרה הם חובה. %-8.2lf משתמש בכל חמשת החלקים.

טבלת ההמרות

מגדירמשמעותארגומנט ל-printfארגומנט ל-scanf
%dמספר שלם עשרוני עם סימןintint *
%iמספר שלם עם סימןint (כמו %d)int * (מקבל גם 0x ואוקטלי עם 0)
%uעשרוני בלי סימןunsigned intunsigned int *
%fנקודה צפה עשרוניתdoublefloat *
%Fכמו %f, עם INF/NAN באותיות גדולותdouble-
%e / %Eכתיב מדעיdoublefloat *
%g / %Gהקצר מבין %f / %edoublefloat *
%a / %Aנקודה צפה הקסדצימליתdoublefloat *
%cתו בודדint (char מקודם)char *
%sמחרוזתchar * (מסתיימת ב-'\0')char * (buffer)
%pכתובת של מצביעvoid *void **
%x / %Xהקסדצימלי בלי סימןunsigned intunsigned int *
%oאוקטלי בלי סימןunsigned intunsigned int *
%nשומר את מספר התווים שנכתבו עד כהint *int *
%%% מילוליאיןמתאים ל-% מילולי
%[...]scanset (רק ב-scanf)-char *

שתי רשומות ראויות לאזהרה. %n כותב לזיכרון במקום להדפיס, והוא המנגנון שמאחורי פגיעות ה-format string: לעולם אל תתנו לטקסט של משתמש להגיע למחרוזת פורמט. ו-%p דורש void *; העברה ישירה של int * היא לא מוגדרת, למרות שכל קומפיילר אמיתי מדפיס את הכתובת בכל מקרה.

מגדירי אורך

מגדיר האורך בא בין הדיוק לאות ההמרה, והוא אומר מה הגודל האמיתי של הארגומנט.

המרות עם סימן (d i) ובלי סימן (u x o) מקבלות טיפוסים שונים: %lu מצפה ל-unsigned long, לא ל-long, וטעות בזה היא התנהגות לא מוגדרת, לא פספוס קוסמטי.

מגדירעם d i (printf / scanf)עם u x o (printf / scanf)
hhint (מ-signed char) / signed char *unsigned int (מ-unsigned char) / unsigned char *
hint (מ-short) / short *unsigned int (מ-unsigned short) / unsigned short *
(אין)int / int *unsigned int / unsigned int *
llong / long *unsigned long / unsigned long *
lllong long / long long *unsigned long long / unsigned long long *
zטיפוס גודל עם סימן / מצביע אליוsize_t / size_t *
jintmax_t / intmax_t *uintmax_t / uintmax_t *
tptrdiff_t / ptrdiff_t *המקביל בלי סימן של ptrdiff_t

ועבור משפחות הנקודה הצפה והתווים:

מגדירעםטיפוס ב-printfטיפוס ב-scanf
(אין)f e g adoublefloat *
lf e g adouble (מתקבל, בלי השפעה)double *
Lf e g along doublelong double *
lc swint_t / wchar_t *wchar_t *

הצירופים שבאמת תקלידו:

את %zu כדאי לזכור בעל פה. sizeof, strlen וכל גודל בספרייה הסטנדרטית מחזירים size_t, שהוא 8 בתים במערכת של 64 ביט, בעוד ש-int הוא 4. printf("%d", strlen(s)) הוא באג אמיתי שנראה עובד בחלק מהפלטפורמות: השתמשו ב-%zu.

המגדירים hh ו-h קיימים בעיקר בשביל scanf, שבו הגודל של היעד חשוב. ב-printf, short מקודם ל-int לפני הקריאה, כך ש-%d היה מדפיס אותו נכון בכל מקרה; %hd רק מתעד את הכוונה.

טיפוסים ברוחב קבוע

עבור טיפוסים מ-<stdint.h> כמו int32_t, המגדירים הניידים הם מאקרו מ-<inttypes.h>:

PRId32 מתרחב לאותיות הנכונות עבור הפלטפורמה שלכם, ומחרוזות מילוליות צמודות מתחברות, ולכן הפורמט מפוצל לשלושה חלקים. זה מכוער; החלופה, המרה ל-long long ושימוש ב-%lld, לעתים קרובות קריאה יותר ותמיד נכונה.

דגלים

דגלים באים מיד אחרי ה-% ומשנים את התצוגה. הם חלים רק על printf.

דגלהשפעהדוגמהפלט
-ריפוד אחרי הערך במקום לפניו%-6d| על 4242 |
+תמיד להדפיס סימן עבור ערכים עם סימן%+d על 42+42
רווחלהדפיס רווח במקום שבו היה +% d על 42 42
0ריפוד באפסים במקום ברווחים%06d על 42000042
#צורה חלופית: 0x עבור %x, 0 עבור %o, שמירת הנקודה עבור %g%#x על 2550xff

אפשר לשלב דגלים: %-+8.2f הוא רוחב מינימלי 8, שתי ספרות אחרי הנקודה, תמיד עם סימן, ריפוד אחרי המספר. - גובר על 0 אם כותבים את שניהם.

רוחב ודיוק

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

המרהמשמעות הדיוק
%f %eספרות אחרי הנקודה העשרונית (ברירת מחדל 6)
%gסך הספרות המשמעותיות (ברירת מחדל 6)
%sמספר תווים מרבי להדפסה
%d %i %u %x %oמספר ספרות מינימלי, מרופד באפסים

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

ב-scanf לרוחב יש תפקיד אחר וחשוב הרבה יותר: הוא מגביל כמה קלט ההמרה תצרוך, וזה הדבר היחיד שעומד בין %s לבין buffer overflow.

איפה printf ו-scanf שונים

הם נראים דומים ולא מסכימים בארבעה מקומות. כל אחד מהם הוא מקור חי לבאגים:

מצבprintfscanf
ערך double%f (float מקודם ל-double)%lf: %f פירושו float *
רוחבגודל שדה מינימלי, מרפדקלט מרבי שנצרך, חותך
ארגומנטיםערכיםמצביעים: &x, או שם של מערך
רווח בפורמטמדפיס רווחמדלג על כל רצף של תווי רווח

נסו 2.5 hello. הפורמט שקורא אותו והפורמט שמדפיס אותו משתמשים באותיות שונות עבור אותו double, וה-15 ב-%15s מגביל את הקלט ל-15 תווים ועוד תו הסיום, בדיוק הגודל של word.

ה-scanset של scanf

%[...] קורא קבוצה של תווים במקום מילה שמופרדת ברווחים. זו הדרך לקרוא שורה שלמה עם scanf, אם כי fgets נשאר הכלי הטוב יותר:

[^\n] פירושו "כל תו חוץ משורה חדשה". %[0-9] יקרא רק ספרות, %[abc] רק את שלוש האותיות האלה. בניגוד ל-%s, scanset לא מדלג על רווחים מובילים, כך ששורה חדשה תועה ב-buffer גורמת לו לא להתאים לכלום.

טעות היא התנהגות לא מוגדרת

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

printf("%d\n", 3.5);        /* מדפיס זבל: קורא את הביטים של double כ-int */
printf("%s\n", 42);         /* מתייחס ל-42 ככתובת: קריסה */
printf("%d\n", strlen(s));  /* size_t נקרא כ-int: שגוי ב-64 ביט */
printf("%f\n", 3);          /* int נקרא כ-double: זבל */

ההגנה עולה דגל אחד. קמפלו עם -Wall ו-GCC יבדוק כל מחרוזת פורמט מילולית מול הארגומנטים שלה:

gcc -Wall -Wextra program.c -o program

הוא מדווח על כל אחת מארבע השורות שלמעלה בזמן קומפילציה. זו האזהרה בעלת הערך הגבוה ביותר ב-C, והיא מופעלת כברירת מחדל בעורך שלמעלה.

לצד המעשי של השימוש בהם, טבלאות מיושרות, הדפסה קריאה של מספרים עשרוניים וקריאת קלט בלי הבאג הקלאסי של השורה החדשה, ראו את העמודים על printf ו-scanf.

שאלות נפוצות

מה המשמעות של %d ב-C?

%d הוא מגדיר ההמרה עבור מספר שלם עשרוני עם סימן, כלומר int. ב-printf הוא מדפיס את הארגומנט כספרות בבסיס 10; ב-scanf הוא קורא ספרות בבסיס 10 לתוך int *. %i אומר אותו דבר ב-printf, אבל ב-scanf %i מקבל גם הקסדצימלי עם 0x ואוקטלי עם אפס מוביל.

מה ההבדל בין %f ל-%lf ב-C?

ב-printf אין הבדל מעשי: ארגומנטים מסוג float מקודמים ל-double, כך ש-%f מדפיס את שניהם, ו-C99 מקבלת את %lf כמילה נרדפת. ב-scanf ההבדל קריטי: %f כותב לתוך float * (4 בתים) ו-%lf לתוך double * (8 בתים). שימוש ב-%f עבור double משחית זיכרון.

איך מדפיסים size_t או long long ב-C?

%zu עבור size_t (מה ש-sizeof ו-strlen מחזירים) ו-%lld / %llu עבור long long / unsigned long long. שימוש ב-%d עבורם הוא התנהגות לא מוגדרת: במקרה זה עובד במערכות של 32 ביט ומתנהג לא נכון במערכות של 64 ביט, ולכן הבאג שורד כל כך הרבה זמן.

מה קורה אם משתמשים ב-format specifier הלא נכון ב-C?

זו התנהגות לא מוגדרת, לא שגיאת עיגול. printf קורא את הארגומנט כמו שהמגדיר טוען שהוא, ולכן printf("%d", 3.5) מדפיס זבל ו-printf("%s", 42) הולך אחרי המספר 42 כאילו היה כתובת ובדרך כלל קורס. קמפלו עם -Wall, שגורם ל-GCC לבדוק מחרוזות פורמט מול הארגומנטים.

איור של שפות התכנות ב-Coddy

ללמוד תכנות עם Coddy

להתחיל