Kürzlich habe ich festgestellt, dass ich in verschiedenen Projekten aktiv bitweise Operationen in PHP schreiben muss. Dies ist eine sehr interessante und nützliche Fähigkeit, die sich vom Lesen von Binärdateien bis zum Emulieren von Prozessoren als nützlich erweist.
PHP verfügt über viele Tools, mit denen Sie Binärdaten bearbeiten können. Ich möchte Sie jedoch sofort warnen: Wenn Sie eine Effizienz auf höchstem Niveau wünschen, ist diese Sprache nichts für Sie.
Und jetzt zur Sache! In diesem Artikel werde ich Ihnen viele interessante Dinge über bitweise Operationen, binäre und hexadezimale Verarbeitung erzählen, die in JEDER Sprache nützlich sein werden.
- Warum PHP möglicherweise nicht der beste Kandidat ist
- Eine kurze Einführung in die binäre und hexadezimale Darstellung von Daten
- Übertragungsvorgänge
- Datendarstellung im Computerspeicher
- Arithmetische Überläufe
- PHP
- : PHP, ?
- PHP
PHP
Ich liebe PHP, versteh mich nicht falsch. Und ich bin sicher, dass diese Sprache in den meisten Fällen gut funktioniert. Wenn Sie jedoch maximale Effizienz bei der Verarbeitung von Binärdaten benötigen, wird PHP dies nicht tun.
Lassen Sie mich erklären: Ich spreche nicht von der Tatsache, dass die Anwendung fünf oder zehn Megabyte mehr verbrauchen kann, sondern davon, eine bestimmte Speichermenge zum Speichern von Daten eines bestimmten Typs zuzuweisen.
Gemäß der offiziellen Dokumentation zu Ganzzahlen repräsentiert PHP Dezimal-, Hexadezimal-, Oktal- und Binärwerte unter Verwendung eines Ganzzahltyps. Es spielt also keine Rolle, welche Daten Sie dort ablegen, es werden immer ganze Zahlen sein.
Sie kennen ZVAL wahrscheinlich bereits - es ist eine C-Struktur, die jede PHP-Variable darstellt. Es hat ein zend_long-Feld, um alle Zahlen darzustellen . Dieses Feld hat einen Typ
lval
, dessen Größe von der Plattform abhängt: Auf 64-Bit-Plattformen wird das Feld als 64-Bit-Nummer und auf 32-Bit-Plattformen als 32-Bit-Nummer dargestellt .
# zval stores every integer as a lval
typedef union _zend_value {
zend_long lval;
// ...
} zend_value;
# lval is a 32 or 64-bit integer
#ifdef ZEND_ENABLE_ZVAL_LONG64
typedef int64_t zend_long;
// ...
#else
typedef int32_t zend_long;
// ...
#endif
Das Fazit lautet: Es spielt keine Rolle, ob Sie 0xff, 0xffff, 0xffffff oder etwas anderes speichern müssen. In PHP werden alle diese Werte so lang ( lval ) mit einer Länge von 32 oder 64 Bit gespeichert .
Zum Beispiel habe ich kürzlich mit der Emulation von Mikrocontrollern experimentiert. Und obwohl es notwendig war, Speicherinhalte und -vorgänge korrekt zu handhaben, brauchte ich nicht zu viel Speichereffizienz, da mein Hosting-Computer Kosten in Größenordnungen kompensierte.
Natürlich ändert sich alles, wenn wir über C-Erweiterungen oder FFI sprechen, aber das ist auch nicht mein Ziel. Ich spreche von reinem PHP.
Denken Sie also daran: Es funktioniert und kann sich so verhalten, wie Sie es möchten, aber in den meisten Fällen verschwenden die Typen den Speicher ineffizient.
Eine kurze Einführung in die binäre und hexadezimale Darstellung von Daten
Bevor Sie darüber sprechen, wie PHP mit Binärdaten umgeht, müssen Sie zunächst darüber sprechen, was Binärdaten sind. Wenn Sie der Meinung sind, dass Sie bereits alles darüber wissen, fahren Sie mit dem Kapitel Binärzahlen und Zeichenfolgen in PHP fort .
In der Mathematik gibt es das Konzept der "Grundlage". Es definiert, wie wir Mengen in verschiedenen Formaten darstellen können. Normalerweise wird die Dezimalbasis (Basis 10) verwendet, mit der wir eine beliebige Zahl mit den Ziffern 0, 1, 2, 3, 4, 5, 6, 7, 8 und 9 darstellen können.
Um das nächste Beispiel zu verdeutlichen, beziehe ich mich auf die Zahl 20 als "Dezimal 20".
Binärzahlen (Basis 2) können eine beliebige Zahl darstellen, jedoch nur mit zwei Ziffern: 0 und 1.
Die Dezimalzahl 20 in Binärform sieht folgendermaßen aus: 0b000 10100 . Sie müssen es nicht selbst in die vertraute Form konvertieren, sondern müssen es von Computern ausführen lassen. ;)
Hexadezimalzahlen (Basis 16) können beliebige Zahlen mit zehn Ziffern 0, 1, 2, 3, 4, 5, 6, 7, 8 und 9 sowie weiteren sechs Zeichen aus dem lateinischen Alphabet darstellen: a, b, c , d, e und f.
Dezimal 20 in hexadezimaler Form sieht folgendermaßen aus: 0x14. Überlassen Sie die Transformation den Computern, sie sind Experten in diesem Bereich!
Es ist wichtig zu verstehen, dass Zahlen in verschiedenen Basen dargestellt werden können: binär (Basis 2), oktal (Basis 8), dezimal (Basis 10, unsere übliche) und hexadezimal (Basis 16).
In PHP und viele andere Sprachen, Binärzahlen sind wie jede andere geschrieben, aber mit dem Präfix 0b : dezimal 20 sieht aus wie 0b 00010100. Hexadezimal - Nummern werden mit 0x voran : dezimal 20 sieht aus wie 0x 14.
Wie Sie bereits wissen vielleicht, Computer speichern keine wörtliche Daten ... Sie werden alle in Form von Binärzahlen, Nullen und Einsen dargestellt. Symbole, Zahlen, Buchstaben, Anweisungen - alles wird in Basis 2 dargestellt. Buchstaben sind nur eine Konvention von Zahlenfolgen. Beispielsweise ist der Buchstabe "a" die Nummer 97 in der ASCII-Tabelle.
Während alles in Binärform gespeichert ist, können Programmierer die Daten am besten im Hexadezimalformat lesen. Sie sehen so besser aus. Schau einfach:
# string "abc"
'abc'
# binary form (bleh)
0b01100001 0b01100010 0b01100011
# hexadecimal form (such wow)
0x61 0x62 0x63
Obwohl das Binärformat visuell viel Platz einnimmt, sind hexadezimale Daten der binären Darstellung sehr ähnlich. Daher verwenden wir sie normalerweise in der Low-Level-Programmierung.
Übertragungsvorgänge
Sie kennen das Konzept des Tragens bereits, aber ich muss darauf achten, damit wir es aus verschiedenen Gründen verwenden können.
Im Dezimalsatz haben wir zehn separate Ziffern, um Zahlen von 0 bis 9 darzustellen. Wenn wir jedoch versuchen, eine Zahl größer als neun darzustellen, verpassen wir die Ziffern! Und hier wird die Übertragungsoperation angewendet: Wir stellen der Zahl die Ziffer 1 voran und setzen die richtige Ziffer auf 0 zurück.
# decimal (base 10)
1 + 1 = 2
2 + 2 = 4
9 + 1 = 10 // <- Carry
Die binäre Basis verhält sich genauso, nur ist sie auf die Ziffern 0 und 1 beschränkt.
# binary (base 2)
0 + 0 = 0
0 + 1 = 1
1 + 1 = 10 // <- Carry
1 + 10 = 11
Es ist dasselbe mit der hexadezimalen Basis, nur dass es einen viel größeren Bereich hat.
# hexadecimal (base 16)
1 + 9 = a // no carry, a is in range
1 + a = b
1 + f = 10 // <- Carry
1 + 10 = 11
Wie Sie verstanden haben, erfordert die Übertragsoperation mehr Ziffern, um bestimmte Zahlen darzustellen. Dies ermöglicht es uns zu verstehen, wie begrenzt bestimmte Datentypen sind und wie begrenzt ihre binäre Darstellung ist, da sie in Computern gespeichert sind.
Datendarstellung im Computerspeicher
Wie oben erwähnt, speichern Computer alles im Binärformat. Das heißt, sie enthalten nur Nullen und Einsen im Speicher.
Es ist am einfachsten, dieses Konzept als große Tabelle mit einer Zeile und vielen Spalten zu visualisieren (soweit die Speicherkapazität dies zulässt. Jede Spalte ist eine Binärzahl (Bit). Die
Darstellung unserer Dezimalzahl 20 in einer solchen Tabelle mit 8 Bits sieht folgendermaßen aus:
| Position (Adresse) | 0 | einer | 2 | 3 | 4 | 5 | 6 | 7 |
| Bisschen | 0 | 0 | 0 | einer | 0 | einer | 0 | 0 |
Eine vorzeichenlose 8-Bit-Ganzzahl ist eine Zahl, die mit maximal 8 Binärzahlen dargestellt werden kann. Das heißt, 0b11111111 (255 Dezimal) ist die größte vorzeichenlose 8-Bit-Zahl. Das Hinzufügen von 1 erfordert die Verwendung einer Übertragsoperation, die nicht mehr mit der gleichen Anzahl von Ziffern dargestellt werden kann.
Wenn wir das wissen, können wir leicht herausfinden, warum es so viele Darstellungen für Zahlen im Speicher gibt und was sie sind: uint8 sind vorzeichenlose 8-Bit-Ganzzahlen (Dezimalzahl 0-255), uint16 sind vorzeichenlose 16-Bit-Ganzzahlen (Dezimalzahl 0-65535) ). Es gibt auch uint32, uint64 und theoretisch höhere.
Vorzeichenbehaftete Ganzzahlen, die negative Werte darstellen können, verwenden normalerweise das letzte Bit, um zu bestimmen, ob sie positiv (letztes Bit = 0) oder negativ (letztes Bit = 1) sind. Wie Sie sich vorstellen können, können Sie damit kleinere Werte in derselben Speichermenge speichern. Die vorzeichenbehaftete 8-Bit-Ganzzahl reicht von -128 bis zur Dezimalzahl 127.
Hier ist die Dezimalzahl -20, die als vorzeichenbehaftete 8-Bit-Ganzzahl dargestellt wird. Beachten Sie, dass das erste Bit gesetzt ist (Adresse 0, Wert 1), dies bedeutet eine negative Zahl.
| Position (Adresse) | 0 | einer | 2 | 3 | 4 | 5 | 6 | 7 |
| Bisschen | einer | 0 | 0 | einer | 0 | einer | 0 | 0 |
Ich hoffe alles ist soweit klar. Diese Einführung ist wichtig, um das Innenleben von Computern zu verstehen. Denken Sie daran, und dann werden Sie immer verstehen, wie PHP unter der Haube funktioniert.
Arithmetische Überläufe
Die ausgewählte Zahlendarstellung (8-Bit, 16-Bit) bestimmt den minimalen und maximalen Wert des Bereichs. Es geht darum, wie Zahlen im Speicher gespeichert werden: Das Hinzufügen von 1 zur Binärziffer 1 führt zu einer Übertragsoperation, dh Sie benötigen ein weiteres Bit als Präfix für die aktuelle Zahl. Da das Ganzzahlformat sehr sorgfältig definiert ist, können wir uns nicht auf Übertragungen außerhalb der Grenzen verlassen (tatsächlich möglich, aber ziemlich verrückt).
| Position (Adresse) | 0 | einer | 2 | 3 | 4 | 5 | 6 | 7 |
| Bisschen | einer | einer | einer | einer | einer | einer | einer | 0 |
Hier sind wir sehr nahe an der 8-Bit-Grenze (255 Dezimalstellen). Wenn wir eine hinzufügen, erhalten wir 255 Dezimalstellen in Binärform:
| Position (Adresse) | 0 | einer | 2 | 3 | 4 | 5 | 6 | 7 |
| Bisschen | einer | einer | einer | einer | einer | einer | einer | einer |
Alle Bits sind zugeordnet! Das Hinzufügen von 1 erfordert eine Übertragsoperation, die nicht möglich ist, da uns die Bits ausgehen, alle 8 sind bereits zugewiesen! Diese Situation nennt man Überlauf , wir überschreiten eine bestimmte Grenze. Die Binäroperation 255 + 2 sollte ein 8-Bit-Ergebnis von 1 ergeben.
| Position (Adresse) | 0 | einer | 2 | 3 | 4 | 5 | 6 | 7 |
| Bisschen | 0 | 0 | 0 | 0 | 0 | 0 | 0 | einer |
Dieses Verhalten ist nicht zufällig, der neue Wert wird nach bestimmten Regeln berechnet, die wir hier nicht berücksichtigen werden.
Binärzahlen und Strings in PHP
Zurück zu PHP! Entschuldigung für diesen großen Exkurs, aber ich denke, es ist wichtig.
Ich hoffe, Sie haben bereits Puzzleteile im Kopf: Binärzahlen, wie sie gespeichert werden, was Überlauf ist, wie PHP Zahlen darstellt ...
Dezimal 20, in PHP als ganzzahliger Wert dargestellt, kann je nach Plattform zwei verschiedene Darstellungen haben ... Auf der x86-Plattform ist es eine 32-Bit-Darstellung, auf der x64 eine 64-Bit-Darstellung, aber in beiden Fällen gibt es ein Vorzeichen (dh der Wert kann negativ sein). Wir wissen, dass die Dezimalzahl 20 in den 8-Bit-Raum passen kann, aber PHP behandelt jede Dezimalzahl als 32 oder 64 Bit.
PHP hat auch binäre Zeichenfolgen, die mit den Funktionen hin und her konvertiert werden können pack () und entpacke () .
In PHP besteht der Hauptunterschied zwischen binären Zeichenfolgen und Zahlen darin, dass Zeichenfolgen einfach Daten enthalten, wie ein Puffer. Mit ganzzahligen Werten (binär und nicht nur) können Sie arithmetische Operationen mit sich selbst ausführen, aber auch binäre (bitweise) Werte wie AND, OR, XOR und NOT.
Binär: Was sollte in PHP, Zahlen oder Strings verwendet werden?
Wir verwenden normalerweise binäre Zeichenfolgen, um Daten zu transportieren. Daher erfordert das Lesen einer Binärdatei oder eines Netzwerks das Packen und Entpacken von Binärzeichenfolgen.
Tatsächliche Operationen wie OR und XOR können jedoch nicht zuverlässig mit Zeichenfolgen ausgeführt werden, sodass Sie Zahlen verwenden müssen.
Debuggen von Binärwerten in PHP
Jetzt lass uns Spaß haben und mit PHP-Code herumspielen!
Zunächst zeige ich Ihnen, wie Sie Daten visualisieren. Wir müssen verstehen, womit wir es zu tun haben.
Das Debuggen von Ganzzahlen ist sehr, sehr einfach. Wir können die Funktion sprintf () verwenden . Es hat eine sehr leistungsfähige Formatierung und hilft uns schnell zu verstehen, mit welchen Werten wir arbeiten.
Stellen wir die Dezimalstelle 20 in 8-Bit-Binär- und 1-Byte-Hexadezimalzahl dar:
<?php
// Decimal 20
$n = 20;
echo sprintf('%08b', $n) . "\n";
echo sprintf('%02X', $n) . "\n";
// Output:
00010100
14
Das Format
%08b
gibt eine Variable in
$n
binärer Darstellung (
b
) mit acht Ziffern (
08
) aus.
Das Format
%02X
zeigt die Variable
$n
in hexadezimaler Notation (
X
) mit zwei Ziffern (
02
) an.
Visualisierung von Binärzeichenfolgen
Obwohl in PHP Ganzzahlen immer 32 oder 64 Bit lang sind, entspricht die Länge der Zeichenfolgen der Länge ihres Inhalts. Um ihre Binärwerte zu dekodieren und zu rendern, müssen wir jedes Byte untersuchen und transformieren.
Glücklicherweise werden Zeichenfolgen in PHP nicht wie Arrays benannt, und jede Position zeigt auf ein 1-Byte-Zeichen. Hier ist ein Beispiel für den Zugriff auf Symbole:
<?php
$str = 'thephp.website';
echo $str[3];
echo $str[4];
echo $str[5];
// Outputs:
php
Angenommen, ein Zeichen ist 1 Byte, können wir ord () aufrufen , um es in eine 1-Byte-Ganzzahl umzuwandeln:
<?php
$str = 'thephp.website';
$f = ord($str[3]);
$s = ord($str[4]);
$t = ord($str[5]);
echo sprintf(
'%02X %02X %02X',
$f,
$s,
$t,
);
// Outputs:
70 68 70
Jetzt können Sie mit der Hexdump-Befehlszeilenanwendung überprüfen:
$ echo 'php' | hexdump
// Outputs
0000000 70 68 70 ...
Die erste Spalte enthält nur die Adresse, und in der zweiten Spalte wir die hexadezimalen Werte , die die Zeichen sehen
p
,
h
und
p
.
Auch beim Umgang mit binären Zeichenfolgen können wir die Funktionen pack () und unpack () verwenden , und ich habe ein großartiges Beispiel für Sie! Angenommen, Sie müssen eine JPEG-Datei lesen, um einige Daten (wie EXIF) zu extrahieren. Im binären Lesemodus können Sie einen Dateihandler öffnen und sofort die ersten beiden Bytes lesen:
<?php
$h = fopen('file.jpeg', 'rb');
// Read 2 bytes
$soi = fread($h, 2);
Um die Werte in ein Integer-Array zu extrahieren, können Sie sie einfach entpacken:
$ints = unpack('C*', $soi);
var_dump($ints);
// Outputs
array(2) {
[1] => int(-1)
[2] => int(-40)
}
echo sprintf('%02X', $ints[1]);
echo sprintf('%02X', $ints[2]);
// Outputs
FFD8
Beachten Sie, dass das C-Format in der Funktion
unpack()
das Zeichen
$soi
als vorzeichenlose 8-Bit-Zahlen in eine Zeichenfolge konvertiert . Der Modifikator
*
entpackt die gesamte Zeile.
Bitweise Operationen
PHP implementiert alle bitweisen Operationen, die Sie möglicherweise benötigen. Sie sind als Ausdrücke eingebaut und das Ergebnis ihrer Arbeit wird nachfolgend beschrieben:
| PHP-Code | Name | Beschreibung |
| $ x | $ y | Inklusive ODER | $ x und $ y wird mit allen angegebenen Bits ein Wert zugewiesen. |
| $ x ^ $ y | Exklusiv oder | $ x oder $ y wird ein Wert mit den angegebenen Bits zugewiesen. |
| $ x & $ y | UND | $ x und $ y wird gleichzeitig ein Wert mit den angegebenen Bits zugewiesen. |
| ~ $ x | NICHT | Ändern Sie die Werte aller Bits in $ x. |
| $ x << $ y | Linksverschiebung | Verschiebt die Bits von $ x um $ y-Positionen nach links. |
| $ x >> $ y | Rechte Shifttaste | Verschiebt die Bits von $ x um $ y-Positionen nach rechts. |
Ich werde erklären, wie jeder funktioniert!
Lass
$x = 0x20
und
$y = 0x30
. Im Folgenden werde ich Beispiele mit binärer Notation zeigen.
Wie Inclusive Or ($ x | $ y) funktioniert
Die Inklusiv-ODER-Operation nimmt alle Bits von beiden Eingängen. Das heißt, es
$x | $y
sollte zurückkehren
0x30
. Schau mal:
// 1 | 1 = 1
// 1 | 0 = 1
// 0 | 0 = 0
0b00100000 // $x = 0x20
0b00110000 // $y = 0x30
OR ------- // $x | $y
0b00110000 // 0x30
Hinweis: Von rechts nach links wurde das sechste Bit
$x
(1) sowie das fünfte und sechste Bit angegeben
$y
. Die Daten wurden gepoolt und mit dem fünften und sechsten Bit ein Wert erzeugt :
0x30
.
Wie Exclusive Or ($ x ^ $ y) funktioniert
Die exklusive ODER-Verknüpfung (auch als XOR bezeichnet) nimmt die Bits nur auf einer Seite. Das heißt, das Ergebnis der Berechnung
$x ^ $y
ist
0x10
:
// 1 ^ 1 = 0
// 1 ^ 0 = 1
// 0 ^ 0 = 0
0b00100000 // $x = 0x20
0b00110000 // $y = 0x30
XOR ------ // $x ^ $y
0b00010000 // 0x10
Wie AND ($ x & $ y) funktioniert
Der AND-Operator ist viel einfacher zu verstehen. Es wendet eine UND-Operation auf jedes Bit an, sodass nur die Werte abgerufen werden, die auf beiden Seiten gleich sind. Das Ergebnis der Berechnung
$x & $y
ist
0x20
:
// 1 & 1 = 1
// 1 & 0 = 0
// 0 & 0 = 0
0b00100000 // $x = 0x20
0b00110000 // $y = 0x30
AND ------ // $x & $y
0b00100000 // 0x20
Wie NICHT (~ $ x) funktioniert
Die NOT-Operation erfordert einen Parameter, sie ändert einfach die Werte aller übertragenen Bits. Es macht alle Nullen zu 1 und alle Einsen zu 0 .:
// ~1 = 0
// ~0 = 1
0b00100000 // $x = 0x20
NOT ------ // ~$x
0b11011111 // 0xDF
Wenn Sie diesen Vorgang in PHP ausgeführt und sich zum Debuggen entschieden haben
sprintf()
, haben Sie wahrscheinlich größere Zahlen bemerkt? Im Kapitel über das Normalisieren von Zahlen werde ich erklären, was hier vor sich geht und wie man es behebt.
Funktionsweise von Left SHIFT und Right SHIFT ($ x << $ n und $ x >> $ n)
Die Bitverschiebung ähnelt dem Multiplizieren oder Teilen von Zahlen mit einer Zweierpotenz. Alle Bits gehen nach
$n
links oder rechts.
Nehmen wir zum Beispiel eine kleine Binärzahl, um die Darstellung zu vereinfachen
$x = 0b0010
. Wenn wir einmal nach
$x
links verschieben , muss dieses eine Bit eine Position nach links verschieben:
$x = 0b0010;
$x = $x << 1;
// 0b0100
Gleiches mit Versatz nach rechts:
$x = 0b0100;
$x = $x >> 2;
// 0b0001
Das heißt, das Verschieben der
$n
Häufigkeit nach links entspricht dem
$n
zweimaligen Multiplizieren , und das Verschieben der
$n
Häufigkeit nach rechts entspricht dem Teilen durch zwei
$n
.
Was ist Bitmaske?
Mit diesen Operationen und anderen Techniken können viele interessante Dinge getan werden. Wenden Sie beispielsweise eine Bitmaske an. Dies ist eine beliebige Binärzahl Ihrer Wahl, die erstellt wurde, um sehr spezifische Informationen zu extrahieren.
Nehmen Sie zum Beispiel an, dass eine vorzeichenbehaftete 8-Bit-Zahl positiv ist, wenn das achte Bit (0) nicht angegeben ist, und negativ, wenn ein Bit angegeben ist. Ist die Zahl positiv oder negativ
0x20
? Was ist mit
0x81
?
Um dies zu beantworten, können wir ein sehr praktisches Byte mit einem einzelnen negativen Bit (
0b10000000
äquivalent
0x80
) und
0x20
UND erstellen . Wenn das Ergebnis ist
0x80
(
0b10000000
, unsere Maske), dann ist dies eine negative Zahl, sonst ist es positiv:
// 0x80 === 0b10000000 (bitmask)
// 0x20 === 0b00100000
// 0x81 === 0b10000001
0x20 & 0x80 === 0x80 // false
0x81 & 0x80 === 0x80 // true
Dies wird häufig bei der Arbeit mit Flags benötigt. Sie können sogar Beispiele für die Verwendung in PHP selbst finden, z. B. Fehlermeldungen .
Sie können auswählen, welche Art von Fehlern generiert werden sollen:
error_reporting(E_WARNING | E_NOTICE);
Was ist hier los? Schauen Sie sich nur Ihre Bedeutung an:
0b00000010 (0x02) E_WARNING
0b00001000 (0x08) E_NOTICE
OR -------
0b00001010 (0x0A)
Wenn PHP eine Benachrichtigung sieht, die gesendet werden kann, prüft es Folgendes:
// error reporting we set before
$e_level = 0x0A;
// Needs to throw a notice
if ($e_level & E_NOTICE === E_NOTICE)
// Flag is set: throws notice
Und Sie werden es überall sehen! Binärdateien, Prozessoren, alles Mögliche auf niedriger Ebene!
Zahlen normalisieren
PHP hat eine Besonderheit in Bezug auf den Umgang mit Binärzahlen: Ganzzahlen sind 32 oder 64 Bit groß. Dies bedeutet, dass wir sie häufig normalisieren müssen, um unseren Berechnungen vertrauen zu können.
Wenn Sie diesen Vorgang beispielsweise auf einem 64-Bit-Computer ausführen, erhalten Sie ein seltsames (aber erwartetes) Ergebnis:
echo sprintf(
'0b%08b',
~0x20
);
// Expected
0b11011111
// Actual
0b1111111111111111111111111111111111111111111111111111111111011111
Was ist hier passiert? Die NOT-Operation für eine 8-Bit-Ganzzahl (
0x20
) hat alle Nullbits in Einsen umgewandelt. Ratet mal, was wir Nullen hatten? Das ist richtig, alle anderen 56 Bits auf der linken Seite, die zuvor ignoriert wurden!
Der Grund ist wiederum, dass in PHP die Länge von Ganzzahlen 32 oder 64 Bit beträgt, unabhängig von ihrem Wert!
Der Code funktioniert jedoch wie erwartet. Das Ergebnis der Operation ~
0x20 & 0b11011111 === 0b11011111
ist beispielsweise ein boolescher Wert (true). Vergessen Sie jedoch nicht, dass diese Bits auf der linken Seite nirgendwo hingehen, da sonst ein seltsames Codeverhalten auftritt.
Um dieses Problem zu lösen, können Sie die Zahlen normalisieren, indem Sie eine Bitmaske anwenden, die alle Nullen löscht. Zum Beispiel zu normalisieren
~0x20
Eine 8-Bit-Ganzzahl muss mit
0xFF
(
0b11111111
) UND-verknüpft werden, damit alle vorherigen 56 Bits zu Nullen werden.
~0x20 & 0xFF
-> 0b11011111
Beachtung! Vergessen Sie nicht, was in Ihren Variablen enthalten ist, da Sie sonst unerwartetes Verhalten erhalten. Schauen wir uns zum Beispiel an, was passiert, wenn wir den obigen Wert ohne 8-Bit-Maske nach rechts verschieben:
~0x20 & 0xFF
-> 0b11011111
0b11011111 >> 2
-> 0b00110111 // expected
(~0x20 & 0xFF) >> 2
-> 0b00110111 // expected
(~0x20 >> 2) & 0xFF
-> 0b11110111 // expected?
Lassen Sie mich erklären: Aus PHP-Sicht wird dies erwartet, da Sie explizit eine 64-Bit-Nummer verarbeiten. Sie müssen verstehen, was IHR Programm erwartet.
Tipp: Vermeiden Sie diese dummen Fehler, indem Sie im TDD-Paradigma programmieren .
Fazit: Binär und PHP sind cool
Sobald Sie mit solchen Tools ausgestattet sind, findet alles andere nur die richtige Dokumentation zum Verhalten von Binärdateien oder Protokollen. Immerhin ist alles binäre Sequenzen.
Ich empfehle dringend, die PDF- oder EXIF-Spezifikationen zu lesen. Vielleicht möchten Sie sogar mit Ihrer eigenen Implementierung des MessagePack- Serialisierungsformats oder Avro, Protobuf experimentieren ... Die Möglichkeiten sind endlos!