מיקום וריפוד#

בסוף הפרק הזה תוכלו לדלג על בתים, להכניס ריפוד, לקפוץ ל־offsets מוחלטים, וליישר שדות לגבולות שרירותיים - ארבע ההוראות x, X, @ ו־..

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

x - דילוג קדימה / הכנסת NUL#

x הוא הפשוט מבין הארבעה. ב־pack הוא מכניס בית NUL אחד מבלי לצרוך ערך; ב־unpack הוא מדלג על בית אחד מבלי לייצר ערך.

pack   "C x C", 65, 66       # "A\0B"
unpack "C x C", "A\0B"       # (65, 66)

עם מונה חזרות, הוא מכניס או מדלג על כך וכך בתים:

pack "C x3 C", 65, 66        # "A\0\0\0B"

x* פירושו ״דילוג לסוף המחרוזת״ ב־unpack, ושווה־ערך ל־x0 (no-op) ב־pack.

x אינו אומר ״רווח״#

הדבר המבלבל ב־x הוא שהוא מרפד ב־NUL, לא ברווח. אם אתם רוצים ריפוד רווחים לטקסט, השתמשו ב־A עם מונה חזרות רחב יותר במקום:

# Wrong - Perl inserts a NUL, not a space
pack "A10 x A10", "date", "label"
# "date      \0label     "

# Right - widen the field
pack "A11 A10", "date", "label"
# "date       label     "

X - נסיגה אחורה#

X מזיז את ראש הכתיבה/קריאה אחורה בבית אחד. עם מונה חזרות, אחורה בכך וכך בתים. בניגוד ל־x, ל־X אין השפעה על בתי הפלט של pack - הוא רק מחזיר את הראש כדי שהוראה עוקבת תדרוס אותם.

pack "CC X C", 65, 66, 67    # "AC" - the second C overwrites 66 with 67

X שימושי בעיקר עם unpack: הציצו קדימה, אז הסיגו אחורה כדי לקרוא מחדש:

# Read a 16-bit length, then re-read the same 2 bytes as two separate bytes
my ($len, $hi, $lo) = unpack "n XX CC", $frame;

X לפני תחילת המחרוזת היא שגיאה קטלנית.

@ - קפיצה למיקום מוחלט#

@N קובע את המיקום הנוכחי לבית N בתוך קבוצת ה־() הפנימית ביותר (או מתחילת המחרוזת אם אין קבוצה עוטפת). ב־pack, זה ממלא באפסים מהמיקום הנוכחי קדימה, או חותך אם המיקום הנוכחי הוא מעבר ל־N:

pack "C @4 C", 65, 66        # "A\0\0\0B"  - byte 0 is 'A', byte 4 is 'B'

זה השווה־ערך ברמת התבנית של offsetof(struct, field): אם קובץ header של C אומר לכם ששדה 2 חי ב־offset 12, @12 מחנה אתכם שם ללא קשר למה שההוראות הקודמות עשו.

@ בתוך קבוצה#

בתוך כל חזרה של קבוצה, @ מתחיל מחדש ב־0. זה עשוי להפתיע אתכם:

pack '@1A((@2A)@3A)', qw(X Y Z)
# "\0X\0\0YZ"

מעבר: ה־@1A החיצוני שם את "X" בבית 1 (עם NUL בבית 0). הקבוצה הפנימית מתחילה מערכת קואורדינטות חדשה במיקום של "X"; בתוכה, @2A שם את "Y" בבית 2 של הקבוצה (שני NULs נוספים בין), ואז @3A שם את "Z" בבית 3 של הקבוצה. קראו את המפרט בקפידה לפני השימוש ב־@ בתוך קבוצות.

. - מיקום מוחלט מהנתונים#

. היא בת הדודה מונעת־הערך של @. במקום מונה חזרות קבוע, המיקום מגיע מהערך הבא ברשימה:

pack "C . C", 65, 4, 66     # "A\0\0\0B" - position 4 from the next value

שימושי כאשר ה־offset מחושב בזמן ריצה - נניח, משדה כותרת שזה עתה קראתם:

my $buf = pack "C .N C", $type, $offset, $value;
# $offset bytes of NUL fill before $value

ב־unpack, . מחזיר את המיקום הנוכחי במקום למלא באפסים:

my ($a, $pos) = unpack "C .", "hello";
# $a = 104 ('h'), $pos = 1

עם .*, ה־offset נמדד מתחילת המחרוזת השלמה; עם .N עבור מספר שלם N 1, מתחילת הקבוצה ה־N־ית העוטפת; עם .0, יחסית למיקום הנוכחי.

המתאם !: יישור#

x!N ו־X!N הופכים את x ו־X לפקודות יישור: התקדמו (או הסיגו) לכפולה הקרובה ביותר של N. כך מכבדים את כללי הריפוד של struct של C.

# struct { char c; double d; char cc[2]; }
my $s = pack "c x![d] d c2", $c, $d, $c1, $c2;

קריאת x![d]: ״יישרו קדימה לכפולה של הרוחב ש־d ארוז היה לוקח״ - כלומר 8 בתים. זה בדיוק הכלל שרוב מהדרי C מחילים על שדה double אחרי char. השתמשו ב־[l!], [d], [Q] כדי לציין את דרישת היישור מבלי להיות צריכים לדעת את הרוחב המספרי.

x!0 או x!1 חשוף הוא no-op.

Checksums - %N ב־unpack בלבד#

%N הוא קידומת על הוראה בודדת, לא הוראה עצמאית. הוא אומר ״במקום להחזיר את הערכים שההוראה הבאה הייתה מייצרת, החזירו את הסכום שלהם ב־N ביטים״. שימושים נפוצים:

my $bytesum = unpack "%32C*", $buf;    # sum every byte
my $setbits = unpack "%32b*", $mask;   # count set bits

קיים רק ב־unpack. אין % ב־pack - אי אפשר לארוז checksum ישירות; חשבו אותו בעצמכם וארזו את התוצאה עם הוראת המספר השלם שאתם צריכים.

דוגמה מפותחת: קריאת payload עם offset משתנה#

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

# Header: magic (4 bytes), version (1 byte), data-offset (4 bytes BE),
# then data starts at the byte-offset the header pointed at.
my ($magic, $ver, $off) = unpack "A4 C N", $file;

die "bad magic" unless $magic eq "MAGI";

# Jump to the data section
my $data = unpack "x$off a*", $file;

שתי קריאות unpack נפרדות - הראשונה קוראת את ה־offset, השנייה משתמשת בו בתבנית. מחרוזות תבנית אינן ביטויים רגולריים; אין להן דרך להציץ קדימה ולהשתמש בערך מחושב בתוך אותה קריאה. בצעו unpack בשלבים כאשר צורת הנתונים תלויה במה שזה עתה קראתם.

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