AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Tutorials Delphi Effizientes Arbeiten mit Bitmasken
Tutorial durchsuchen
Ansicht
Themen-Optionen

Effizientes Arbeiten mit Bitmasken

Ein Tutorial von DeddyH · begonnen am 2. Jul 2007 · letzter Beitrag vom 15. Sep 2007
Antwort Antwort
Seite 2 von 2     12   
Benutzerbild von DeddyH
DeddyH
Registriert seit: 17. Sep 2006
Ich habe mich entschlossen, dieses kleine Tutorial zu schreiben, weil mir in letzter Zeit auffiel, dass viele User mit Bitmasken so ihre Schwierigkeiten haben. Also will ich einmal versuchen, ein wenig Licht ins Dunkel zu bringen.

Wozu braucht man das eigentlich?

Jeder Programmierer wird im Laufe der Zeit auf sog. Flags stoßen (gerade auch im Bereich der Win32-API). In vielen (wenn nicht gar in den meisten) Fällen verbirgt sich dahinter nichts anderes als eine ganze Zahl. Je nach ihrer Breite kann in dieser Zahl eine gewisse Anzahl von Bool' schen Informationen (Attributen) untergebracht werden. Hierzu mal ein kleines Beispiel:
Delphi-Quellcode:
//fiktive Konstanten für die Dateiverarbeitung
const
  flLesen = 1;
  flSchreiben = 2;
  flAusfuehren = 4;
  flAnzeigen = 8;
  flLoeschen = 16;

var Flags: byte = 0; //Vorbelegung -> kein Flag gesetzt
Zum Vergleich einmal eine Lösung, die wahrscheinlich viele Anfänger verwenden würden:
Delphi-Quellcode:
//Verwendung einzelner Bool' scher Variablen
var Lesen, Schreiben, Ausfuehren, Anzeigen, Loeschen: Boolean;

procedure TMyForm.FormCreate(Sender: TObject);
begin
  Lesen := false;
  Schreiben := false;
  Ausfuehren := false;
  Anzeigen := false;
  Loeschen := false;
end;
Auf den ersten Blick ist, abgesehen von der Länge des Quelltextes, kein großer Unterschied erkennbar. Interessant wird es aber spätestens dann, wenn man diese Attribute speichern und wieder auslesen möchte. Nehmen wir als Art der Speicherung einmal die gute alte Ini-Datei. Zum 2. Beispiel könnte diese z.B. so aussehen:
Delphi-Quellcode:
[Attributes]
Lesen=1
Schreiben=0
Ausfuehren=1
Anzeigen=0
Loeschen=0
Nun dasselbe mit dem 1. Beispiel:
Delphi-Quellcode:
[Attributes]
Flags=5
Stellt man sich das Ganze dann noch mit Speicherung in einer Datenbank vor, sollten die Vorteile der binären Lösung (der Bitmaske) jedem klar sein.

Wie wird es benutzt (oder "Binär ist gar nicht schwär")?

Zunächst ein ganz kleiner Exkurs in die binäre Darstellung von Zahlen (der Einfachheit halber lediglich ganzer Zahlen ohne Vorzeichen).
Die kleinste unteilbare Einheit stellt in der Informatik das Byte dar. Jedes Byte besteht aus 8 Bit. Jedes Bit repräsentiert den Wert einer 2er-Potenz (aufsteigend von rechts nach links) und kann entweder den Wert 0 oder 1 annehmen. Das niederwertigste (ganz rechte) Bit hat die Wertigkeit 2^0, was Eins entspricht. Je weiterer Stelle nach links verdoppelt sich diese Wertigkeit.

Nehmen wir als Beispiel mal die Zahl 29, welche in binärer Darstellung so aussieht (auf ein Byte ausgerichtet):
00011101 Errechnet wird das so (von rechts nach links): 1*1 + 0*2 + 1*4 + 1*8 + 1*16 + 0*32 + 0*64 + 0*128
Erweitert man diese Zahl auf Word (= 16 Bit) oder Cardinal (= 32 Bit), so werden lediglich 8 bzw. 24 Stellen nach links mit Nullen aufgefüllt, an der Berechnung ändert sich gar nichts.
Wer Spaß daran hat, kann ja mal alle Bits mit 1 belegen und die resultierende Zahl abfragen oder errechnen. Plötzlich fällt es einem wie Schuppen aus den Haaren, wieso MAXBYTE ausgerechnet 255 ist
Aufmerksame Leser werden bemerkt haben, dass ich in meinem 1. Beispiel die Konstanten so gewählt habe, dass sie Potenzen von 2 entsprechen. Der Grund dafür dürfte nun auch klar sein.

Setzen und Auswerten, aber wie?

In Delphi kennen wir die folgenden binären Operatoren (bitte genau lesen und merken):
and - Das Ergebnis ist dann 1, wenn beide zu vergleichenden Bits 1 sind
or - Das Ergebnis ist dann 1, wenn mindestens eins der zu vergleichenden Bits 1 ist
xor - Das Ergebnis ist dann 1, wenn genau eins der zu vergleichenden Bits 1 ist

Außerdem (für uns in diesem Zusammenhang nicht relevant und daher nur der Vollständigkeit halber erwähnt)
shl - Verschiebung aller Bits um die angegebene Weite nach links
shr - Verschiebung aller Bits um die angegebene Weite nach rechts

Auswerten

Kommen wir zurück zu unserem 1. Beispiel und nehmen an, dass die Variable Flags die Zahl 5 enthält (entspricht Lesen und Ausführen).
Um nun zu ermitteln, ob wir lesen dürfen, bedienen wir uns einer Abfrage wie dieser:
Delphi-Quellcode:
if (Flags and flLesen) > 0 then ...
//Alternativ:
if (Flags and flLesen) = flLesen then ...
Somit findet folgender Vergleich statt (and):
Delphi-Quellcode:
00000101 //Flags
00000001 //flLesen
--------
00000001 // = 1 (flLesen)
Das Ganze funktioniert analog für alle anderen Flags.

Setzen

Hier wird es scheinbar etwas komplizierter.
Wir möchten beispielsweise zusätzlich das Löschen erlauben (in unserem Beispiel 16, d.h. Bit 5 von rechts). Im ersten Impuls würde man vermutlich sagen
Flags := Flags and flLoeschen; Schauen wir uns das wieder in binärer Schreibweise an (and):
Delphi-Quellcode:
00000101 //Flags
00010000 //flLoeschen
--------
00000000 // = 0
*Oops*, das ist aber nicht das beabsichtigte Ergebnis. Wir müssen or verwenden.
Flags := Flags or flLoeschen; Kontrolle (or):
Delphi-Quellcode:
00000101 //Flags
00010000 //flLoeschen
--------
00010101 // = 21
Aha, so sollte das aussehen

Nun wollen wir sicherstellen, dass nicht gelöscht werden darf. Dazu muss ein "and"-Vergleich mit dem negierten Flag durchgeführt werden. Negieren bedeutet hier, dass alle Bits des Flags ihren Wert ändern (0 in 1 und umgekehrt).
Flags := Flags and not flLoeschen; Wir vergleichen (bei gesetztem Flag):
Delphi-Quellcode:
00010101 //Flags
11101111 //not flLoeschen
--------
00000101 // = 5
Und nun bei nicht gesetztem Flag:
Delphi-Quellcode:
00000101 //Flags
11101111 //not flLoeschen
--------
00000101 // = 5
Wie wir sehen, wird das gewünschte Ergebnis in beiden Fällen erreicht.

Um ein bestimmtes Flag zu negieren, kann man das "Exklusive Oder" (xor) verwenden.
Flags := Flags xor flLoeschen; Nach Durchlauf 1 (xor):
Delphi-Quellcode:
00000101 //Flags
00010000 //flLoeschen
--------
00010101 // = 21
Nach Durchlauf 2 (xor):
Delphi-Quellcode:
00010101 //Flags
00010000 //flLoeschen
--------
00000101 // = 5
Fallstricke

Gelegentlich sieht man (sogar in Fachbüchern) die Verwendung von + und - zum Setzen bzw. Entfernen von Flags. Dies erscheint auf den ersten Blick logischer als mit or, and etc. zu arbeiten, denn wir wollen ja eigentlich rechnen. Trotzdem ist davon dringendst abzuraten. Wieso? Der Grund ist ganz einfach der: es funktioniert nur unter ganz gewissen Bedingungen.
Kurz zum Hintergrund: bei der Addition zweier Bits passiert Folgendes:
Delphi-Quellcode:
0 + 0 = 0
0 + 1 = 1
1 + 1 = 0 + Übertrag auf das nächsthöhere Bit
Bei der Subtraktion hingegen
Delphi-Quellcode:
0 - 0 = 0
0 - 1 = 1 + negativer Übertrag auf das nächsthöhere Bit
1 - 1 = 0
Dazu mal ein paar Beispiele:
Flags := Flags + flLoeschen; Ist Bit 4(Loeschen) nicht gesetzt, geht das auch gut (Addition).
Delphi-Quellcode:
00000101 //Flags
00010000 //flLoeschen
--------
00010101 // = 21
Anders sieht es aus, wenn das Flag bereits gesetzt war (Addition).
Delphi-Quellcode:
00010101 //Flags
00010000 //flLoeschen
--------
00100101 // = 37
Umgekehrt (Entfernen des Flags) stellt sich das so dar:
Flags := Flags - flLoeschen; Flag ist gesetzt (Subtraktion):
Delphi-Quellcode:
00010101 //Flags
00010000 //flLoeschen
--------
00000101 // = 5
Flag ist nicht gesetzt (Subtraktion):
Delphi-Quellcode:
00000101 //Flags
00010000 //flLoeschen
--------
11110101 // = 245
Das ist ganz übel: wir haben eine ungewollte Kettenreaktion ausgelöst! Wenn die höherwertigen Bits auch an anderer Stelle im Programm ausgewertet werden, haben wir die Kappe auf. Deshalb mein dringender Rat: niemals so machen .

Das soll es an dieser Stelle gewesen sein. Ich hoffe, dem geneigten Leser ist die Thematik etwas klarer geworden. Außerdem sei auf die Ergänzungen von Hagen verwiesen, was Zahlensysteme und Bitverschiebungen angeht (Nochmals Danke, Hagen).

Für Anregungen, Ergänzungen und sonstige Kritik bin ich immer offen, nur keine Scheu.

//Edit: Ich hatte die Variable im Beispiel Dateiattribute genannt und im Folgenden immer von Flags geredet. Es heißt nun durchgehend Flags. Außerdem habe ich die Anregungen von Christian beherzigt und die Addition und Subtraktion eingefügt.
"Ich habe Angst vor dem Tag, an dem die Technologie unsere menschlichen Interaktionen übertrumpft. Die Welt wird eine Generation von Idioten bekommen." (Albert Einstein)
Dieser Tag ist längst gekommen

Geändert von DeddyH (18. Jun 2010 um 09:51 Uhr)
 
Benutzerbild von xZise
xZise

 
Delphi 2009 Professional
 
#11
  Alt 15. Sep 2007, 17:57
Zitat von jfheins:
Das and liefert nur 1, wenn beide operanden gleich 1 sind
Fabian
  Mit Zitat antworten Zitat
Antwort Antwort
Seite 2 von 2     12   


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 10:32 Uhr.
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz