Maison> développement back-end> C++> le corps du texte

Un aperçu des opérations au niveau du bit

WBOY
Libérer: 2024-07-29 09:50:22
original
963 Les gens l'ont consulté

An Overview of Bitwise Operations

Le message suivant est tiré d'un référentiel que j'ai créé pour aider à apprendre (et enseigner) les opérations au niveau des bits. Vous pouvez trouver ce dépôt ici, et je vous suggère de le consulter : vous y trouverez des exemples de code et des solutions.

Introduction

Le but de ce référentiel est de vous familiariser avec les opérations au niveau du bit, en expliquant ce qu'elles sont, comment elles fonctionnent et à quoi elles peuvent être utilisées.

Chapitre 1 : Tout est binaire

En C (et dans la plupart des langages de haut niveau), nos variables ont destypes. Ces types sont révélateurs de plusieurs choses. Bien sûr, une variable de type int stockera une valeur entière, mais la clé pour comprendre ces opérations au niveau du bit est de savoir que sous le capot, tous les types sont stockés en mémoire (n'importe où, pile, tas, n'importe où) sous forme binaire. Voici un exemple de ce qui se passe lorsque vous stockez une simple valeur entière sur la pile dans C:

int main(int argc, char** argv) { int x = 2; return 0; }
Copier après la connexion

Après la compilation en assembly, le code pourrait ressembler à ceci (j'utilise ici l'assembly ARM et j'ai annoté le code à l'aide de commentaires) :

.section .text .global main main: ; Set up the stack for the function stp x29, x30 [sp, -16]! ; Save previous frame pointer & link register mov x29, sp ; Setup a new frame pointer ; Initialize x with 2 ; IN C: int x = 2; mov w0, 2 ; Move 2 into the w0 register str w0, [sp, 16] ; Store the contents of w0 (2) at a 16-byte offset from the stack pointer ; Essentially, the above line stores 2 on the stack. mov w0, 0 ; Move 0 into w0, prepare for return ; Clear stack ldp x29, x30, [sp], 32 ; Restore frame pointer and link register ret ; Return
Copier après la connexion

Notez que la plupart des compilateurs ne stockeraientpasréellement une variable comme celle que j'ai montrée sur la pile, car elle n'est pas utilisée. Cependant, s'il est utilisé plusieurs fois, il sera stocké sur la pile, comme ci-dessus.

Si nous regardions l'emplacement où notre variable a été stockée sur la pile (alors qu'elle est là, bien sûr), nous verrions quelque chose comme :

Adresse mémoire Valeur stockée (Hex) Valeur stockée (binaire)
0x1000 0x02 00000010
0x1001 0x00 00000000
0x1002 0x00 00000000
0x1003 0x00 00000000

Cela suppose que votre système est petit-boutiste. Je n'entrerai pas dans les détails de l'endianisme ici, mais vous pouvez en savoir plus ici.

La chose clé que j'aimerais que vous remarquiez à propos du tableau ci-dessus est que même si notre entier ne fait que 2 bits, il occupe 4 octets (32 bits) de mémoire. Maintenant, ne paniquez pas, c'est normal et attendu. L'une des nombreuses choses que font C et votre compilateur est de définir des normes pour les types que vous invoquez. Ainsi, lorsque je crée une variable int, le compilateur sait allouer 4 octets (encore une fois, 32 bits) de mémoire. Nous pouvons même tester cela en utilisant l'opérateur sizeof() en C.

L'opérateur sizeof()

sizeof() n'est pas une véritable fonction C. Au lieu de cela, au moment de la compilation, le compilateur remplace l'expression par la taille du type de données donné. Vous pouvez même l'utiliser avec vos propres types, comme les typedefs et/ou les structures :

#include  typedef struct { char name[64]; int age; } Person; int main(int argc, char** argv) { printf("A Person is %lu bytes long.\n", sizeof(Person)); return 0; }
Copier après la connexion

Une autre chose que vous vous demandez peut-être est de savoir comment les nombres négatifs sont stockés. Excellente question. Les numéros peuvent êtresignésounon signés, mais par défaut, ils sont signés. Si un entier est signé, il sacrifie son bit le plus significatif pour devenir le « bit de signe ». Si le bit de signe est 1, le nombre est négatif ; sinon c'est positif. Un lecteur avisé se rendra peut-être compte que le changement qui se produit ici se situe dans la gamme des nombres possibles. Considérez les nombres de 8 bits. Il y a 256 nombres possibles à représenter (donnés par 2^8). Avec un entier non signé de 8 bits, les valeurs 0 à 255 peuvent être représentées ; avec un entier 8 bits signé, -128-127 peut être représenté.

Pour obtenir l'inverse d'un nombre binaire, utilisez le complément de deux. Trouvons -5 en binaire.

  1. Commencez par 5. En binaire, 5 vaut 0101. Le 0 en tête est le signe.
  2. Inversez chaque bit. 0101 → 1010.
  3. Ajoutez 1 à ce numéro (en ignorant tout éventuel débordement). 1010 + 0001 = 1011.

À ton tour!

  1. Confirmez que -5 est 1011 en binaire en effectuant un compliment de deux dessus pour obtenir 5, ou 0101.
  2. Écrivez un programme C qui imprime la taille d'un entier en octets et en bits. Utilisez le code ci-dessus comme point de départ.Astuce : pour convertir des octets en bits, combien de bits y a-t-il dans un octet ?
  3. Remplissez le tableau suivant avec des tailles de différents types, en modifiant votre programme pour les vérifier.
Type Taille (octets) Taille (bits)
int
int64_t
int8_t
char
bool (vous devrez #include )
long int
int court
long long int
double
double

Sample Responses

Question 1

  1. Start with -5: 1011.
  2. Invert each bit: 1011 → 0100.
  3. Add 1: 0100 + 0001 = 0101

Question 2

Here's an example of what your simple program might look like (you can also check it out at Chapter 1/SizeOfOperatorTest.c).

#include  int main(int argc, char** argv) { printf("The size of an int is %lu bytes, or %lu bits.\n", sizeof(int), sizeof(int) * 8); return 0; }
Copier après la connexion

Go ahead and compile it using gcc and check out its output:

cd Chapter\ 1 gcc -o sizeof SizeOfOperatorTest.c ./sizeof
Copier après la connexion

Question 3

Type Size (bytes) Size (bits)
int 4 32
int64_t 8 64
int8_t 1 8
char 1 8
bool (you'll need to #include ) 1 8
long int 4 32
short int 2 16
long long int 8 64
double 4 32
double 8 64

TakeThisHome

The main point I'd like you to keep in mind is thatwith control of every bit, we can optimize our memory usage. Though that has little effect on modern systems, in the case of embedded computing, every byte counts.By manually reading and writing bits as opposed to typical variable values, we can harness more functionality from less storage.

Chapter 2: Operating on Bits

Now that we've covered data types and how data is stored, we're ready to introduce the idea of bitwise operations. Put simply, abitwise operationis an operation done on each bit of a piece of data. Let's take a look at each bitwise operator. Then, we'll implement them in C.

And (&)

Written x & y in C. This operator takes the corresponding bits of each number and performs an and operation. An and operation returns 1 (true)if and only if both bits are 1. This means that two bits that are both 0 donotreturn 1—they return 0. The result is the number made up of the binary string of results from each and. It's easiest to see this in a truth table.

Consider the operation int result = 3 & 5. First, convert 3 and 5 to binary.
Now, we have int result = 011 & 101. Consider the following truth table:

Bit A Bit B AND
0 1 0
1 0 0
1 1 1

The result of the and operation is 001, which when converted to decimal is 1.

Or (|)

Written x | y in C. This operator takes the corresponding bits of each number and performs an or operation. An or operation returns 1if either bit is 1. As with other bitwise operators, the result is the number made up of the binary string of results from each or.

Consider the operation int result = 3 | 5. First, convert 3 and 5 to binary.
Now, we have int result = 011 | 101. Consider the following truth table:

Bit A Bit B OR
0 1 1
1 0 1
1 1 1

The result of the or operation is 111, which when converted to decimal is 7.

Xor (^)

Written x ^ y in C. This operator takes the corresponding bits of each number and performs an xor (exclusive or) operation. An xor operation returns 1if and only if one of the two bits is 1. As with other bitwise operators, the result is the number made up of the binary string of results from each or.

Consider the operation int result = 3 ^ 5. First, convert 3 and 5 to binary.
Now, we have int result = 011 ^ 101. Consider the following truth table:

Bit A Bit B XOR
0 1 1
1 0 1
1 1 0

Das Ergebnis der xor-Operation ist 110, was bei der Umrechnung in eine Dezimalzahl 6,

ergibt

Linksverschiebung (<<)

Geschrieben x << Betrag Im Gegensatz zu den oben genannten Operatoren arbeitet dieser Operator nur mit einer Zahl. Jedes Bit in der angegebenen Zahl wird um den angegebenen Betrag nach links verschoben. Alle Bits, die das Ende der Zahl erreichen, werden abgeschnitten (und auf der rechten Seite erscheinen Nullen). Lassen Sie uns ein Beispiel durchgehen.

Consider int result = 5 << 2. Wie Sie wissen, ist 5 binär 101. Lassen Sie uns jede Iteration der Schicht durchgehen.

Anfänglich

1 0 1

Nach einer Schicht

0 1 0

Ergebnis

1 0 0

Das binäre Ergebnis ist 100, also 4 in Dezimalzahl.

Rechtsverschiebung (>>)

Geschrieben x >> Betrag Dieser Operator ähnelt der Linksverschiebung, funktioniert jedoch in die entgegengesetzte Richtung. Jedes Bit in der angegebenen Zahl wird um den angegebenen Betrag nach rechts verschoben. Alle Bits, die das Ende der Zahl erreichen, werden abgeschnitten (und auf der linken Seite erscheinen Nullen). Lassen Sie uns ein Beispiel durchgehen.

Consider int result = 3 >> 2. Wie Sie wissen, ist 3 binär 011. Lassen Sie uns jede Iteration der Schicht durchgehen.

Anfänglich

0 1 1

Nach einer Schicht

0 0 1

Ergebnis

0 0 0

Das binäre Ergebnis ist 000, was 0 in Dezimalzahl ist.

Nicht (~)

Geschrieben ~x. Der Not-Operator invertiert alle Bits der angegebenen Zahl. Zur Erläuterung verwenden wir noch einmal eine Wahrheitstabelle.

Betrachten Sie das int-Ergebnis = ~5. Wie Sie wissen, ist 5 binär 101.

Bit A ~ Bit A
1 0
0 1
1 0

Daher ist das Ergebnis der Nicht-Operation 010, was binär 2 ist.

Einschränkungen für die Links- und Rechtsverschiebung

Für diesen Schichtbetrieb gelten einige nennenswerte Einschränkungen. Zunächst einmal können Sie Bits nicht um eine negative Anzahl verschieben – das macht einfach keinen Sinn! Außerdem gilt das Verschieben um mehr als die Anzahl der Ihrer Variablen zugewiesenen Bits alsundefiniert. Siekönnenes tun, aber es ist nicht garantiert, dass die Ausgabe für einen bestimmten Wert konstant ist. Obwohl es sich hierbei nicht um eine Einschränkung per se handelt, führt eine Verschiebung um 0 einfach nicht zu einer Verschiebung.

Du bist dran!

  1. Füllen Sie für jede der folgenden Aussagen eine Wahrheitstabelle aus. Betrachten Sie alle Werte als vorzeichenlos. Nach Abschluss in eine Dezimalzahl umwandeln.
  2. 8 & 2
  3. 6 | 3
  4. 7 ^ 5
  5. (5 & 2) & (4 & 3)
  6. (5 | 2) & (4 & 3)
  7. (5 & 2) ^ (4 | 3)

  8. Schließen Sie jeden Vorgang ab. Betrachten Sie alle Werte als vorzeichenlos und solange der längste Wert im Problem erforderlich ist (d. h. wenn Sie den größten Wert von 8 haben, müssen Sie sich mit 4 Bits befassen). Konvertieren Sie es nach Abschluss in eine Dezimalzahl.

  9. ~6

  10. 9 << 4 (hier wird davon ausgegangen, dass der Wert eine Länge von 32 hat, sodass Platz für eine Linksverschiebung vorhanden ist).

  11. ~(7 & 8)

  12. (2 | 6) >> 1

  13. 8 >> (~2)

  14. ~((3 >> 2) ^ ~7) & (~(7 >> 4) | 2)

Beispielantworten

Frage 1

  • 8 & 2 → 1000 & 0010
Bit A Bit B UND
1 0 0
0 0 0
0 1 0
0 0 0

⇒ 0000, was 0 in Dezimalzahl ist.

  • 6 | 3 → 110 | 011
Bit A Bit B ODER
1 0 1
1 1 1
0 1 1

⇒ 111, also 7 in Dezimalzahl.

  • 7 ^ 5 → 111 ^ 101
Bit A Bit B XOR
1 1 0
1 0 1
1 1 0

⇒ 010, was 2 in Dezimalzahl ist.

  • (5 & 2) & (4 & 3) → (101 & 010) & (100 & 011)
Bit A Bit B A und B Bit C Bit D C UND D (A UND B) UND (C UND D)
1 0 0 1 0 0 0
0 1 0 0 1 0 0
1 0 0 0 1 0 0

⇒ 000, was 0 in Dezimalzahl ist.

  • (5 | 2) & (4 & 3) → (101 | 010) & (100 & 011)
Bit A Bit B A ODER B Bit C Bit D C UND D (A ODER B) UND (C UND D)
1 0 1 1 0 0 0
0 1 1 0 1 0 0
1 0 1 0 1 0 0

⇒ 000, was 0 in Dezimalzahl ist.

  • (5 & 2) ^ (4 | 3) → (101 & 010) ^ (100 | 011)
Bit A Bit B A und B Bit C Bit D C ODER D (A UND B) XOR (C ODER D)
1 0 0 1 0 1 1
0 1 0 0 1 1 1
1 0 0 0 1 1 1

⇒ 111, which is 7 in decimal.

Question 2

  • ~6 → ~110 ⇒ 011, which is 3 in decimal.

  • 9 << 4 → 001001 << 4 ⇒ 100100, which is 36 in decimal.

  • ~(7 & 8) → ~(0111 & 1000) → ~(0000) ⇒ 1111, which is 15 in decimal.

  • (2 | 6) >> 1 → (010 | 110) >> 1 → 110 >> 1 ⇒ 011, which is 3 in decimal.

  • 8 >> (~2) → 1000 >> ~(10) → 1000 >> (01) → 1000 >> 1 ⇒ 0100, which is 4 in decimal.

  • ~((3 >> 2) ^ ~7) & (~(7 >> 4) | 2)

To solve this, I suggest splitting into halves:

~((3 >> 2) ^ ~7) and (~(7 >> 4) | 2)

~((3 >> 2) ^ ~7) → ~((011 >> 2) ^ ~(111)) → ~((000) ^ ~(111)) → ~(000 ^ 000) → 111
(~(7 >> 4) | 2) → (~(111 >> 4) | 010) → (~(000) | 010) → (111 | 010) → 111

Hence, 111 & 111 ⇒ 111, which is 7 in decimal.

Chapter 3: Applying Bitwise Operations in C

This chapter is all about writing C code that utilizes bitwise operators. Before we get to doing bitwise operations, we should begin by writing a function that can write the binary equivalent of a given variable.

To do this, we use a mask. We initialize it to contain a 1 in the most significant (leftmost in little-endian systems) bit followed by zeros. With each iteration of a loop, we right shift the mask by 1, moving the 1 all the way "across" the mask. When we use the & operator on the pointer and the mask, any non-zero value means that a 1 occurred somewhere in the result. Since there's only one 1 in the mask, we know exactly where this occurred. Since the loop moves from left to right, we can just append the corresponding bit's character to the string. The string is one character longer than the size because it needs to contain the null character (\0). This is how printf knows to stop printing, and omitting it can lead to numerous errors and/or unexpected behaviors (like the printing of other data from in memory).

void printBinary(unsigned int decimal) { // To determine size (in bits), we multiply the maximum size of an unsigned int by 8 (to convert from bytes to bits). int size = sizeof(decimal) * 8; // This counts the leading zeros of the number, then subtracts them from the size. // Hence, we start at the first bit set to 1 (unless decimal == 0) size -= __builtin_clz(decimal); if(size == 0) size = 1; // Handle zero case, we need one space to display "0." int curr = 0; char binary[size + 1]; for(unsigned int mask = 1 << (size - 1); mask != 0; mask >>= 1) { if((decimal & mask) != 0) { binary[curr] = '1'; } else { binary[curr] = '0'; } curr++; } binary[curr] = '\0'; printf("%s", binary); }
Copier après la connexion

Bitwise Assignment Operators

All bitwise operators, except for not (~), can be used in the assignment fashion. This means you can add an equals sign right next to one of the bitwise operator. For example, in

int a = 2; a &= 7;
Copier après la connexion

a is both the first operand and the destination. In other words, the value of a & 7 is stored in a. This works for all bitwise operators aside from the not (~) operator.

Now, I'd like to provide a few case study like prompts for you to try. Sample responses are available.

Case Study 1: Unix File Permissions

One use case of bitwise operations is in the Unix permission system. You've probably run the command

chmod 777 some-file
Copier après la connexion

But what do each of the numbers mean? And why 7? The reality is, binary is at play here, and 7 should tip you off. Recall that 7 in binary is 111. The number being passed here is acting as three flags or booleans glued together.

The three digits specified are for three classes of users:

  • The file owner;
  • Group members of the file owner;
  • and everyone else.

As I mentioned above, each digit is a conglomeration of three flags, each representing a permission. The flag (binary bit) in the fours place represents read permission, the twos place is for write permission, and the ones is for execute. So,

chmod 777 some-file

is doing this under the hood:

File Permissions: some-file

Group Read Write Execute Decimal
Owner 1 1 1 7
Owner's Group 1 1 1 7
Everyone Else 1 1 1 7

Mit anderen Worten, alle Berechtigungen werden allen erteilt.

Aufgabe

Entwerfen Sie einen einfachen Berechtigungsprüfer, der einen vollständigen Dateiberechtigungswert (eine dreistellige Zahl) aufnimmt und nach einem bestimmten Satz spezifischer Berechtigungen sucht (z. B. Schreiben durch den Besitzer, Ausführen durch alle usw.). Ein Beispiel finden Sie im Ordner Kapitel 3.

Hinweis

Nachdem Sie eine vollständige Zahl eingegeben haben, müssen Sie diese in ein int (von einem char*) umwandeln. Verwenden Sie dann modulare Arithmetik, um jeden Berechtigungssatz aufzuschlüsseln. Denken Sie daran, dass die erste Ziffer die Berechtigungen des Eigentümers darstellt, die zweite für die Benutzergruppe des Eigentümers und die dritte für alle.

Um zu überprüfen, ob eine bestimmte Berechtigung in einem Berechtigungssatz vorkommt, bitweise und die gegebene Berechtigung mit dem Satz.

Fall 2: Subnetting eines Netzwerks

Wenn Sie jemals einen Router konfiguriert haben, ist Ihnen möglicherweise eine Option zum Konfigurieren der „Subnetzmaske“ aufgefallen. Normalerweise ist dies auf 255.255.255.0 eingestellt, aber warum? Zunächst müssen wir bedenken, dass jedes Byte einer IPv4-Adresse durch ein „.“ getrennt ist. Wenn Sie es mit dem Netzwerktyp zu tun haben, mit dem Sie am besten vertraut sind (ein Netzwerk der Klasse C), sind 3 Bytes für das Netzwerk reserviert und das letzte Byte ist für den Host.

Da es sich bei der Subnetzmaske um eine Maske handelt, verstehen Sie vielleicht schon, welchen Zweck sie hat. Für jedes Bit, das Sie vom Host-Byte „ausleihen“, werden zwei Subnetze erstellt.

Netzwerkadresse

Bei derNetzwerkadressesind alleHost-Bits auf 0 gesetzt. Dies bedeutet, dass jedes Bit zum Erstellen übergeben wird
Ein Subnetz könnte immer noch auf 1 gesetzt werden.

Mehr lesen!

Erfahren Sie mehr über Subnetze auf dieser Website.

Aufgabe

Schreiben Sie in C ein Programm, das eine IPv4-Adresse und eine Subnetzmaske aufnimmt und die Netzwerkadresse findet und ausgibt, in der sich die IPv4-Adresse befindet. Ein Beispiel finden Sie im Ordner Kapitel 3.

Hinweis

Sie müssen jedes Byte der Adresse und Maske als numerischen Wert speichern. Um die Netzwerkadresse zu finden, überlegen Sie, welche (bitweise) Operation zwischen Maske und Adresse den beabsichtigten Effekt erzeugt.

Schließen

Ich hoffe, dieser Erklärer war hilfreich für Sie! Ich habe es geschrieben, weil ich selbst etwas über bitweise Operationen lernen wollte. Ich habe es überprüft, aber einige Dinge könnten falsch sein. Korrigieren Sie mich also gerne über eine Pull-Anfrage oder fügen Sie sogar einen Kommentar hinzu! Wenn Sie Fragen haben, hinterlassen Sie außerdem einen Kommentar! Ich kann es kaum erwarten, mit Ihnen zu chatten! Abschließend möchte ich sagen, dass ich sehr froh bin, Ihnen diese Ressource zur Verfügung stellen zu können!

Über mich

Hallo! Ich bin Jackson, Student der Informatik und Französisch am Lafayette College und ein aufstrebender Forscher und Professor für Informatik. Ich interessiere mich derzeit für die Bereiche Bioinformatik und Low-Level-Programmierung/Systeme. Um mehr über mich zu erfahren, schauen Sie sich meine Website an.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal
À propos de nous Clause de non-responsabilité Sitemap
Site Web PHP chinois:Formation PHP en ligne sur le bien-être public,Aidez les apprenants PHP à grandir rapidement!