Endianness#

בסוף הפרק הזה תוכלו לבחור בין סדר בתים native, big-endian ו־little-endian; להשתמש בקיצורים הניידים n / N / v / V; ולהחיל את המתאמים < / > על כל מספר שלם או float.

מספר שלם רב־בתי חייב להיכתב באופן כלשהו. 0x12345678 הוא ארבעה בתים - אך באיזה סדר? שתי מוסכמות שולטות:

  • Big-endian - בית בעל הסדר הגבוה ביותר ראשון: 12 34 56 78. בשימוש על־ידי רוב פרוטוקולי הרשת, על־ידי מעבדי PowerPC ו־SPARC, ועל־ידי פורמטים של קבצים המתעניינים בניידות (PNG, Java .class, רוב כותרות TCP/IP).

  • Little-endian - בית בעל הסדר הנמוך ביותר ראשון: 78 56 34 12. בשימוש על־ידי x86, x86-64, ARM (במצביו הנפוצים), ועל־ידי פורמטים של קבצים משושלת Intel (BMP, WAV, TIFF בווריאנט הנפוץ שלו, כל FAT).

ערבוב ביניהם יוביל לקריאת שטויות. pack נותן לכם שלוש דרכים לקבוע את סדר הבתים.

הקיצורים הניידים: n / N / v / V#

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

הוראה

בתים

Endian

משמעות

n

2

big-endian

רשת (מספרי פורט)

N

4

big-endian

רשת (כתובות IPv4)

v

2

little-endian

״VAX״

V

4

little-endian

״VAX״

״Network byte order״ הוא big-endian. אם אתם קוראים RFC, התשובה היא כמעט תמיד n או N.

my $port = pack "n", 443;          # "\x01\xbb"
my $ip   = pack "N", 0x7F000001;   # "\x7f\x00\x00\x01" - 127.0.0.1

ארבעת אלה הם ללא־סימן בלבד. עם המתאם ! הם הופכים לעם סימן:

my $neg = pack "n!", -1;            # "\xff\xff"

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

המתאמים הכלליים: < ו־>#

כל הוראת מספר שלם (s, S, l, L, i, I, q, Q, j, J) וכל float (f, d, F, D) מקבלים מתאם endianness:

מתאם

משמעות

מנמוניקה

>

Big-endian

ה־״big end״ נוגע באות ההוראה

<

Little-endian

ה־״little end״ נוגע בהוראה

קראו l> כ־״long, big end״ ו־l< כ־״long, little end״.

my $be32 = pack "l>", -1_000_000;    # big-endian 32-bit signed
my $le64 = pack "q<", 2 ** 40;       # little-endian 64-bit signed
my $bed  = pack "d>", 3.14159;       # big-endian IEEE 754 double

למקרים הנפוצים של 16- ו־32-ביט ללא־סימן, n / N / v / V קצרים יותר ויש להעדיף אותם. השתמשו ב־< / > כאשר:

  • השדה הוא עם סימן - n ו־N הם ללא־סימן כברירת מחדל

  • השדה הוא 64-ביט - אין מקבילות n/N עבור q/Q

  • השדה הוא float - n/N מכסים מספרים שלמים בלבד

  • אתם רוצים להחיל endianness על קבוצה שלמה (ראו למטה)

החלת endianness על קבוצה#

אם כל מספר שלם בתת־מבנה חולק את אותו סדר בתים, כתבו את מתאם ה־endianness פעם אחת על קבוצה:

my $rec = pack "(s l l)<", $flags, $x, $y;
# same as "s<l<l<"

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

סדר native: מתי הוא כן מה שאתם רוצים#

ההוראות החשופות s / S / l / L / i / I / q / Q מייצרות את סדר הבתים native של מעבד המארח. זה שימושי כאשר:

  • אתם בונים struct של C בזיכרון לקריאת ioctl על אותה מכונה.

  • אתם כותבים קובץ זמני שרק התוכנית הזו תקרא בחזרה.

  • מפרט הפרוטוקול אומר במפורש ״host byte order״ (נדיר).

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

איתור סדר הבתים של המארח#

שימושי פעם אחת, כבדיקת שפיות. ארזו 1 כמספר שלם 16-ביט והסתכלו על הבית הראשון:

my $little_endian = unpack("c", pack("s", 1)) == 1;
my $big_endian    = unpack("c", pack("s", 1)) == 0;

על x86 הראשון אמת; על מארח big-endian השני אמת.

גם ל־float יש endianness#

IEEE 754 אינו קובע סדר בתים. double שנכתב על מכונה little-endian יוצא הפוך על מכונה big-endian:

# send a float in big-endian order - safe across machines that use IEEE 754
my $buf = pack "d>", 2.718281828;

חומרה שאינה תואמת (פורמטי float שאינם IEEE, פריסות long double אקזוטיות) לא ניתנת לתיקון על־ידי < / > בלבד. לניידות בין פלטפורמות מוזרות, שלחו float כטקסט או השתמשו בפורמט מדויק־בבתים כמו הפלט של sprintf "%a".

דוגמה מפותחת: פענוח כותרת BMP#

פורמט הבִּיטמַפ BMP מתחיל ב־ASCII magic BM ולאחריו שלושה שדות 32-ביט little-endian (גודל קובץ, שמור, offset של נתוני פיקסל):

my ($magic, $filesize, $reserved, $pixoff) =
    unpack "A2 V V V", $bmp_header;

die "not a BMP" unless $magic eq "BM";

V הוא 32-ביט little-endian ללא־סימן - בדיוק מה שהמפרט דורש. A2 קורא שני בתים כמחרוזת טקסט ללא הסרת רווחים. כל שדה הוא בצורה הנכונה; לא נדרשת החלפת בתים, אין התנהגות תלוית־פלטפורמה.

מה לזכור#

  • פורמט חוט? השתמשו ב־n / N / v / V - או ב־< / > אם אתם צריכים עם־סימן, 64-ביט או float.

  • נשארים בתוך תהליך אחד? native הוא תקין - אך סמנו את הבחירה במכוון.

  • IEEE 754 אינו מספיק. אם float חוצה מכונה, קבעו את סדר הבתים.

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