Der Ringpuffer (ring buffer, circular buffer) wird häufig in speicherbegrenzten Systemen, Gerätetreibern, Netzwerken und Multitasking-Systemen verwendet. Sein Konzept wurde bereits in den frühen Phasen der Betriebssystementwicklung verwendet, als es wichtig war, den Speicher optimal zu nutzen und keine Ressourcen für das Verschieben von Elementen nach dem Abrufen zu verschwenden.
Typische Schwierigkeiten sind die korrekte Behandlung der Bedingungen „Puffer voll“ und „Puffer leer“, das Fehlen eines Schutzes gegen Datenüberschreibungen und die komplizierte Synchronisation bei Multithreading. Fehler können durch Verwirrung der Grenzen und falsche Überprüfung der Indizes auftreten.
Das Objekt des Ringpuffers wird durch ein Array, zwei Indizes (head und tail) und gegebenenfalls eine Zählervariable oder zusätzliche Logik zur Unterscheidung der Zustände „voll“ und „leer“ implementiert. Das Lesen und Schreiben erfolgt modulo der Größe des Puffers.
#define BUF_SIZE 8 char buffer[BUF_SIZE]; int head = 0, tail = 0; // head – Schreiben, tail – Lesen // Schreiben if (((head + 1) % BUF_SIZE) != tail) { buffer[head] = data; head = (head + 1) % BUF_SIZE; } else { // Puffer voll } // Lesen if (head != tail) { char d = buffer[tail]; tail = (tail + 1) % BUF_SIZE; }
Schlüsselmerkmale:
Wie unterscheidet man einen vollständig gefüllten Puffer von einem vollständig leeren?
Typischerweise wird ein leerer Puffer durch head == tail identifiziert. Für einen vollen Puffer kann man entweder einen Speicherplatz leer lassen oder die Anzahl der Elemente in einem expliziten Zähler speichern.
Kann man den Puffer in einer Multithreading-Umgebung ohne Sperren verwenden?
Nein, im Standardfall sind bei gleichzeitiger Lese- und Schreibvorgängen Rennen möglich. Man muss entweder atomare Operationen oder Sperren verwenden.
Was passiert, wenn man das Überlaufen von head oder tail nicht kontrolliert?
Bei einem Überlauf kommt es zu einem Übertreten der Arraygrenzen, was zu undefiniertem Verhalten und möglicher Speicherbeschädigung führt.
Ein Entwickler implementierte einen Ringpuffer, bei dem head == tail sowohl als „leer“ als auch als „voll“ interpretiert wurde, wodurch das Signal für das Überlaufen verloren ging.
Vorteile:
Nachteile:
Stattdessen wurde eine Zählervariable für die Elemente hinzugefügt oder ein Slot reserviert, sodass head tail niemals vollständig einholt.
Vorteile:
Nachteile: