April 26, 2023

CPP הטעות הנפוצה בקריאת קבצים בשפת

אובייקטי stream

בשפת C++ משתמשים באובייקטי Stream על מנת לקרוא ולכתוב מידע לקבצים.
כל מתכנת יודע את הדוגמא הקלאסית לכתיבה לתצוגה:

1
std::cout << "Hello world\n";

בעזרת סטרימים אנחנו יכולים לקרוא ולכתוב מידע בעזרת האופרטור << או >>.

אובייקטי fstream

האובייקטים מחולקים ל-input ו-output.
ולכן יש 3 אובייקטים:

std::fstream.
std::ifstream.
std::ifstream.

בכללי יש לנו הרבה אובייקטי IO ממליץ לקרוא על האדרים :

https://en.cppreference.com/w/cpp/io

איך הסרטימים בנויים?
מאחורי הקלעים הם משתמשים במחלקה אשר מממשת את הצורה הכי בסיסית בלקרוא ולכתוב מידע.
ה-std::filebuf.
הבאפר הנ”ל מממש את פונקציות הכתיבה והקריאה וההזזה של המצביע לקובץ בסטרים.

האובייקט std::fstream מממש גם כתיבה וגם קריאה.
וכן שני האובייקטים הנותרים מממשים כתיבה וקריאה בלבד בהתאמה: std::ofstream, std::ifstream.

כל פתיחה של קובץ מאלצת אותנו לבחור כיצד יש לפתוח את הקובץ, על פי איזה מצב.
המצב מתואר כאן:
https://en.cppreference.com/w/cpp/io/ios_base/openmode

מצב הסבר
app לפני כתיבה מחפש את הנקודה האחרונה בקובץ כדי לוודא שכותבים לסוף הקובץ ולא לאמצע
binary פותח קובץ בינארי
in פותח לכתיבה
out פותח לקריאה
trunc מוחק את התוכן של הסטרים לפני פתיחה
ate מחפש את סוף הטרים בפתיחה
noreplace לא מוחק קובץ קיים, אם קיים תיווצר שגיאה

אז מהי הבעיה הנפוצה בטיפול בקבצים בינאריים?

דרך נפוצה לקרוא קובץ:

1
2
std::fstream file{ "file.bin",  std::fstream::in };
std::vector<char> buffer(std::istreambuf_iterator<char>(file), {});

שימו לב לפי הטבלה צריך לשים פרמטר הנקרא binary אך בדוגמת הקוד הזו - שכחנו!

תוכן הקובץ:

1
01 03 04 00 02 04 05 00 0D 0A 03 04 00

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

שימו לב לקובץ שהוא מכיל 0D 0A בתור מזהה וכמות.
ב-ASCII התווים האלו אומרים \r\n שזה:

1
2
\r - Carriage Return
\n - Line feed

קבצים טקסטואליים מכילים שורות אשר מסתיימות בתווים הנ”ל ואף פרוטוקולים טקסטואליים מגדירים את השימוש בתווים האלו לחציצה בין תווים.
למשל בפרוטוקול Http.

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

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

כדי לפתוח אותו באופן בינארי נוסיף את ההגדרה המתאימה לבנאי:

1
2
std::fstream file{ "file.bin",  std::fstream::in   | std::fstream::binary};
std::vector<char> buffer(std::istreambuf_iterator<char>(file), {});

וזהו!


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

תודה על הקריאה!

על הפוסט

הפוסט נכתב על ידי Ilya, רישיון על ידי CC BY-NC-ND 4.0.

שתפו את הפוסט

Email Facebook Linkedin Print

קנו לי קפה

#Software#CPP