Endianness#

Στο τέλος αυτού του κεφαλαίου θα μπορείτε να επιλέγετε μεταξύ native, big-endian, και little-endian σειράς bytes· να χρησιμοποιείτε τις φορητές συντομεύσεις n / N / v / V· και να εφαρμόζετε τους τροποποιητές < / > σε οποιονδήποτε ακέραιο ή float.

Ένας ακέραιος πολλαπλών bytes πρέπει να γραφεί κάπως. Το 0x12345678 είναι τέσσερα bytes - αλλά με ποια σειρά; Δύο συμβάσεις κυριαρχούν:

  • Big-endian - το byte υψηλότερης τάξης πρώτο: 12 34 56 78. Χρησιμοποιείται από τα περισσότερα πρωτόκολλα δικτύου, από τις CPU PowerPC και SPARC, και από πρότυπα αρχείων που νοιάζονται για τη φορητότητα (PNG, Java .class, τα περισσότερα headers TCP/IP).

  • Little-endian - το byte χαμηλότερης τάξης πρώτο: 78 56 34 12. Χρησιμοποιείται από x86, x86-64, ARM (στις συνηθισμένες λειτουργίες του), και από πρότυπα αρχείων από τη γενιά της Intel (BMP, WAV, TIFF στη συνηθισμένη παραλλαγή του, ολόκληρο το FAT).

Μπερδέψτε τα και θα διαβάσετε ασυναρτησίες. Η pack σας δίνει τρεις τρόπους να καθηλώσετε τη σειρά bytes.

Οι φορητές συντομεύσεις: n / N / v / V#

Τέσσερα γράμματα καλύπτουν τις πιο συνηθισμένες περιπτώσεις μορφής επί της γραμμής:

Οδηγία

Bytes

Endian

Σημασία

n

2

big-endian

Δίκτυο (αριθμοί θυρών)

N

4

big-endian

Δίκτυο (διευθύνσεις IPv4)

v

2

little-endian

«VAX»

V

4

little-endian

«VAX»

Η «σειρά bytes δικτύου» είναι 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"

Αν το πεδίο σας ταιριάζει σε ένα από αυτά τα τέσσερα σχήματα, χρησιμοποιήστε τα. Είναι φορητά, είναι ευανάγνωστα, και η σημασία τους δεν εξαρτάται από τη CPU που τρέχει τον κώδικα.

Οι γενικοί τροποποιητές: < και >#

Οποιαδήποτε οδηγία ακεραίου (s, S, l, L, i, I, q, Q, j, J) και κάθε float (f, d, F, D) δέχεται τροποποιητή endianness:

Τροποποιητής

Σημασία

Μνημονικό

>

Big-endian

Η «μεγάλη άκρη» αγγίζει το γράμμα της οδηγίας

<

Little-endian

Η «μικρή άκρη» αγγίζει την οδηγία

Διαβάστε το l> ως «long, μεγάλη άκρη» και το l< ως «long, μικρή άκρη».

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-bit, τα n / N / v / V είναι συντομότερα και προτιμώνται. Χρησιμοποιήστε τα < / > όταν:

  • το πεδίο είναι προσημασμένο - τα n και N είναι μη προσημασμένα από προεπιλογή

  • το πεδίο είναι 64-bit - δεν υπάρχουν αντίστοιχα n/N για τα q/Q

  • το πεδίο είναι float - τα n/N καλύπτουν μόνο ακεραίους

  • θέλετε να εφαρμόσετε endianness σε ολόκληρη μια ομάδα (δείτε παρακάτω)

Εφαρμογή endianness σε ομάδα#

Αν κάθε ακέραιος σε μια υπο-δομή μοιράζεται την ίδια σειρά bytes, γράψτε τον τροποποιητή endianness μία φορά σε μια ομάδα:

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

Ένας τροποποιητής σε επίπεδο ομάδας διαχέεται σε κάθε οδηγία με σειρά bytes που βρίσκεται μέσα, συμπεριλαμβανομένων των εμφωλευμένων ομάδων. Οι οδηγίες που δεν δέχονται τροποποιητή σειράς bytes (όπως η C) αγνοούνται σιωπηλά.

Native σειρά: όταν είναι αυτό που θέλετε#

Οι γυμνές οδηγίες s / S / l / L / i / I / q / Q παράγουν τη native σειρά bytes της CPU του υπολογιστή. Αυτό είναι χρήσιμο όταν:

  • Κατασκευάζετε ένα struct της C στη μνήμη για μια κλήση ioctl στην ίδια μηχανή.

  • Γράφετε ένα πρόχειρο αρχείο που μόνο αυτό το πρόγραμμα θα διαβάσει πίσω.

  • Οι προδιαγραφές του πρωτοκόλλου λένε ρητά «host byte order» (σπάνιο).

Είναι λάθος όταν τα δεδομένα φεύγουν από τη διεργασία: ένα αρχείο που γράφεται σε laptop και διαβάζεται σε διακομιστή δεν πρέπει να χρησιμοποιεί native σειρά εκτός αν συμβαίνει και οι δύο μηχανές να συμφωνούν.

Ανίχνευση της σειράς bytes του υπολογιστή#

Χρήσιμο μία φορά, ως έλεγχος ορθότητας. Πακετάρετε το 1 ως ακέραιο 16-bit και κοιτάξτε το πρώτο byte:

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 δεν καθηλώνει τη σειρά bytes. Ένα 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;

Το ασύμβατο hardware (μορφές float που δεν είναι IEEE, εξωτικές διατάξεις long double) δεν μπορεί να επιδιορθωθεί μόνο με τα < / >. Για φορητότητα σε ασυνήθιστες πλατφόρμες, στείλτε τα float ως κείμενο ή χρησιμοποιήστε μια μορφή ακριβή σε byte όπως η έξοδος του sprintf "%a".

Επεξεργασμένο παράδειγμα: ανάλυση ενός header BMP#

Η μορφή bitmap BMP ξεκινάει με τη μαγική ASCII BM ακολουθούμενη από τρία πεδία little-endian 32-bit (μέγεθος αρχείου, δεσμευμένο, offset δεδομένων pixel):

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

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

Το V είναι little-endian 32-bit μη προσημασμένο - ακριβώς αυτό που απαιτούν οι προδιαγραφές. Το A2 διαβάζει δύο bytes ως συμβολοσειρά κειμένου χωρίς αποκοπή. Κάθε πεδίο έχει το σωστό σχήμα· δεν απαιτείται εναλλαγή bytes, δεν υπάρχει συμπεριφορά εξαρτώμενη από την πλατφόρμα.

Τι να θυμάστε#

  • Μορφή επί της γραμμής; Χρησιμοποιήστε n / N / v / V - ή < / > αν χρειάζεστε προσημασμένο, 64-bit, ή float.

  • Παραμένετε εντός μίας διεργασίας; Το native είναι εντάξει - αλλά επισημάνετε την επιλογή σκόπιμα.

  • Το IEEE 754 δεν αρκεί. Αν ένα float διασχίζει μια μηχανή, καθηλώστε τη σειρά bytes.

Με τους μετρητές bytes και τη σειρά bytes υπό έλεγχο, οι υπόλοιπες οδηγίες - συμβολοσειρές, τοποθέτηση, ομάδες - προκύπτουν φυσιολογικά.