Ich benötige einen Ringpuffer mit fester Größe (zur Laufzeit beim Erstellen auswählbar, nicht zur Kompilierungszeit), der Objekte aller Art aufnehmen kann und eine sehr hohe Leistung aufweisen muss. Ich denke nicht, dass es Probleme mit Ressourcenkonflikten geben wird, da es sich zwar um eine eingebettete Multitasking-Umgebung handelt, es sich jedoch um eine kooperative handelt, sodass die Aufgaben dies selbst verwalten können.
Mein erster Gedanke war, eine einfache Struktur im Puffer zu speichern, die den Typ (einfache Aufzählung / Definition) und einen ungültigen Zeiger auf die Nutzlast enthält, aber ich möchte, dass dies so schnell wie möglich ist, damit ich für Vorschläge offen bin, die das Umgehen beinhalten der Haufen.
Eigentlich bin ich froh, eine der Standardbibliotheken für die Rohgeschwindigkeit zu umgehen - nach dem, was ich vom Code gesehen habe, ist er nicht stark für die CPU optimiert: Es sieht so aus, als hätten sie nur C-Code für Dinge wie strcpy()
und so kompiliert , es gibt keine handcodierte Baugruppe.
Jeder Code oder Ideen wäre sehr dankbar. Die erforderlichen Operationen sind:
- Erstellen Sie einen Puffer mit einer bestimmten Größe.
- am Schwanz setzen.
- vom Kopf bekommen.
- Geben Sie die Zählung zurück.
- Puffer löschen.
quelle
Antworten:
Können Sie die Typen auflisten, die zum Zeitpunkt des Codierens des Puffers benötigt werden, oder müssen Sie in der Lage sein, Typen zur Laufzeit über dynamische Aufrufe hinzuzufügen? Wenn erstere, würde ich den Puffer als ein Heap-zugewiesenes Array von n Strukturen erstellen, wobei jede Struktur aus zwei Elementen besteht: einem Enum-Tag, das den Datentyp identifiziert, und einer Vereinigung aller Datentypen. Was Sie an zusätzlichem Speicherplatz für kleine Elemente verlieren, machen Sie dadurch wieder wett, dass Sie sich nicht mit Zuweisung / Freigabe und der daraus resultierenden Speicherfragmentierung befassen müssen. Dann müssen Sie nur noch die Start- und Endindizes verfolgen, die die Kopf- und Endelemente des Puffers definieren, und beim Erhöhen / Dekrementieren der Indizes sicherstellen, dass mod n berechnet wird.
quelle
Die einfachste Lösung wäre, die Artikelgröße und die Anzahl der Artikel zu verfolgen und dann einen Puffer mit der entsprechenden Anzahl von Bytes zu erstellen:
typedef struct circular_buffer { void *buffer; // data buffer void *buffer_end; // end of data buffer size_t capacity; // maximum number of items in the buffer size_t count; // number of items in the buffer size_t sz; // size of each item in the buffer void *head; // pointer to head void *tail; // pointer to tail } circular_buffer; void cb_init(circular_buffer *cb, size_t capacity, size_t sz) { cb->buffer = malloc(capacity * sz); if(cb->buffer == NULL) // handle error cb->buffer_end = (char *)cb->buffer + capacity * sz; cb->capacity = capacity; cb->count = 0; cb->sz = sz; cb->head = cb->buffer; cb->tail = cb->buffer; } void cb_free(circular_buffer *cb) { free(cb->buffer); // clear out other fields too, just to be safe } void cb_push_back(circular_buffer *cb, const void *item) { if(cb->count == cb->capacity){ // handle error } memcpy(cb->head, item, cb->sz); cb->head = (char*)cb->head + cb->sz; if(cb->head == cb->buffer_end) cb->head = cb->buffer; cb->count++; } void cb_pop_front(circular_buffer *cb, void *item) { if(cb->count == 0){ // handle error } memcpy(item, cb->tail, cb->sz); cb->tail = (char*)cb->tail + cb->sz; if(cb->tail == cb->buffer_end) cb->tail = cb->buffer; cb->count--; }
quelle
// Note power of two buffer size #define kNumPointsInMyBuffer 1024 typedef struct _ringBuffer { UInt32 currentIndex; UInt32 sizeOfBuffer; double data[kNumPointsInMyBuffer]; } ringBuffer; // Initialize the ring buffer ringBuffer *myRingBuffer = (ringBuffer *)calloc(1, sizeof(ringBuffer)); myRingBuffer->sizeOfBuffer = kNumPointsInMyBuffer; myRingBuffer->currentIndex = 0; // A little function to write into the buffer // N.B. First argument of writeIntoBuffer() just happens to have the // same as the one calloc'ed above. It will only point to the same // space in memory if the calloc'ed pointer is passed to // writeIntoBuffer() as an arg when the function is called. Consider // using another name for clarity void writeIntoBuffer(ringBuffer *myRingBuffer, double *myData, int numsamples) { // -1 for our binary modulo in a moment int buffLen = myRingBuffer->sizeOfBuffer - 1; int lastWrittenSample = myRingBuffer->currentIndex; int idx; for (int i=0; i < numsamples; ++i) { // modulo will automagically wrap around our index idx = (i + lastWrittenSample) & buffLen; myRingBuffer->data[idx] = myData[i]; } // Update the current index of our ring buffer. myRingBuffer->currentIndex += numsamples; myRingBuffer->currentIndex &= myRingBuffer->sizeOfBuffer - 1; }
Solange die Länge Ihres Ringpuffers eine Zweierpotenz ist, wird die unglaublich schnelle binäre "&" -Operation Ihren Index für Sie umschließen. Für meine Anwendung zeige ich dem Benutzer ein Audiosegment aus einem Ringpuffer von Audio an, der von einem Mikrofon erfasst wurde.
Ich stelle immer sicher, dass die maximale Menge an Audio, die auf dem Bildschirm angezeigt werden kann, viel geringer ist als die Größe des Ringpuffers. Andernfalls lesen und schreiben Sie möglicherweise aus demselben Block. Dies würde Ihnen wahrscheinlich seltsame Anzeigeartefakte geben.
quelle
uint8_t tmp1,tmp2; tmp1 = (34 + 1) & 31; tmp2 = (35 ) % 32; printf("%d %d",tmp1,tmp2);
Also, was ist der eigentliche Unterschied oder es ist nur ein Codierungsstil?Erstens die Überschrift. Sie benötigen keine Modulo-Arithmetik, um den Puffer zu umbrechen, wenn Sie Bit-Ints verwenden, um die "Zeiger" von Kopf und Schwanz zu halten und sie so zu dimensionieren, dass sie perfekt synchron sind. IE: 4096, gefüllt mit einem 12-Bit-Int ohne Vorzeichen, ist 0 für sich und in keiner Weise belästigt. Das Eliminieren der Modulo-Arithmetik, selbst bei Zweierpotenzen, verdoppelt die Geschwindigkeit - fast genau.
10 Millionen Iterationen zum Füllen und Entleeren eines 4096-Puffers mit Datenelementen aller Art dauern auf meinem Dell XPS 8500 der 3. Generation i7 mit dem C ++ - Compiler von Visual Studio 2010 mit Standard-Inlining 52 Sekunden, und 1/8192. davon, um ein Datum zu warten.
Ich würde RX die Testschleifen in main () neu schreiben, damit sie den Fluss nicht mehr steuern - was durch die Rückgabewerte gesteuert wird und sollte, die anzeigen, dass der Puffer voll oder leer ist, und die damit verbundene Unterbrechung; Aussagen. IE: Der Füllstoff und der Abfluss sollten in der Lage sein, ohne Korruption oder Instabilität gegeneinander zu schlagen. Irgendwann hoffe ich, diesen Code mit mehreren Threads zu versehen, woraufhin dieses Verhalten entscheidend sein wird.
Die Funktion QUEUE_DESC (Warteschlangendeskriptor) und die Initialisierung erzwingen, dass alle Puffer in diesem Code eine Potenz von 2 haben. Das obige Schema funktioniert sonst NICHT. Beachten Sie, dass QUEUE_DESC nicht fest codiert ist, sondern eine Manifestkonstante (#define BITS_ELE_KNT) für seine Konstruktion verwendet. (Ich gehe davon aus, dass eine Potenz von 2 hier ausreichend Flexibilität ist)
Um die Laufzeit der Puffergröße auswählbar zu machen, habe ich verschiedene Ansätze (hier nicht gezeigt) ausprobiert und mich für die Verwendung von USHRTs für Head, Tail, EleKnt entschieden, die einen FIFO-Puffer [USHRT] verwalten können. Um Modulo-Arithmetik zu vermeiden, habe ich eine Maske für && mit Head, Tail erstellt, aber diese Maske stellt sich als (EleKnt -1) heraus. Verwenden Sie sie also einfach. Die Verwendung von USHRTS anstelle von Bit-Ints erhöhte die Leistung auf einer leisen Maschine um ~ 15%. Intel-CPU-Kerne waren schon immer schneller als ihre Busse. Wenn Sie also auf einem ausgelasteten, gemeinsam genutzten Computer Ihre Datenstrukturen packen, werden Sie vor anderen konkurrierenden Threads geladen und ausgeführt. Kompromisse.
Beachten Sie, dass der tatsächliche Speicher für den Puffer mit calloc () auf dem Heap zugewiesen wird und sich der Zeiger an der Basis der Struktur befindet, sodass die Struktur und der Zeiger genau dieselbe Adresse haben. IE; Es muss kein Offset zur Strukturadresse hinzugefügt werden, um Register zu binden.
In diesem Sinne befinden sich alle Variablen, die mit der Wartung des Puffers verbunden sind, physisch neben dem Puffer und sind in dieselbe Struktur eingebunden, sodass der Compiler eine schöne Assemblersprache erstellen kann. Sie müssen die Inline-Optimierung beenden, um eine Baugruppe zu sehen, da sie sonst in Vergessenheit gerät.
Um den Polymorphismus eines beliebigen Datentyps zu unterstützen, habe ich memcpy () anstelle von Zuweisungen verwendet. Wenn Sie nur die Flexibilität benötigen, einen Zufallsvariablentyp pro Kompilierung zu unterstützen, funktioniert dieser Code einwandfrei.
Für Polymorphismus müssen Sie nur den Typ und die Speicheranforderungen kennen. Das DATA_DESC-Array von Deskriptoren bietet eine Möglichkeit, jedes Datum zu verfolgen, das in QUEUE_DESC.pBuffer abgelegt wird, damit es ordnungsgemäß abgerufen werden kann. Ich würde nur genug pBuffer-Speicher zuweisen, um alle Elemente des größten Datentyps aufzunehmen, aber verfolgen, wie viel von diesem Speicher ein bestimmtes Datum tatsächlich in DATA_DESC.dBytes verwendet. Die Alternative besteht darin, einen Heap-Manager neu zu erfinden.
Dies bedeutet, dass der UCHAR * pBuffer von QUEUE_DESC über ein paralleles Begleitarray verfügt, um den Datentyp und die Größe zu verfolgen, während der Speicherort eines Datums in pBuffer unverändert bleibt. Das neue Mitglied wäre so etwas wie DATA_DESC * pDataDesc oder vielleicht DATA_DESC DataDesc [2 ^ BITS_ELE_KNT], wenn Sie einen Weg finden, Ihren Compiler mit einer solchen Vorwärtsreferenz zur Übermittlung zu bewegen. Calloc () ist in diesen Situationen immer flexibler.
Sie würden noch memcpy () in Q_Put (), Q_Get, aber die Anzahl der tatsächlich kopierten Bytes wird durch DATA_DESC.dBytes bestimmt, nicht durch QUEUE_DESC.EleBytes. Die Elemente haben möglicherweise alle unterschiedliche Typen / Größen für einen bestimmten Put oder Get.
Ich glaube, dieser Code erfüllt die Anforderungen an Geschwindigkeit und Puffergröße und kann erstellt werden, um die Anforderungen für 6 verschiedene Datentypen zu erfüllen. Ich habe die vielen Testgeräte in Form von printf () -Anweisungen belassen, damit Sie sich davon überzeugen können (oder nicht), dass der Code ordnungsgemäß funktioniert. Der Zufallszahlengenerator zeigt, dass der Code für jede zufällige Kopf / Schwanz-Kombination funktioniert.
enter code here // Queue_Small.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include <stdio.h> #include <time.h> #include <limits.h> #include <stdlib.h> #include <malloc.h> #include <memory.h> #include <math.h> #define UCHAR unsigned char #define ULONG unsigned long #define USHRT unsigned short #define dbl double /* Queue structure */ #define QUEUE_FULL_FLAG 1 #define QUEUE_EMPTY_FLAG -1 #define QUEUE_OK 0 // #define BITS_ELE_KNT 12 //12 bits will create 4.096 elements numbered 0-4095 // //typedef struct { // USHRT dBytes:8; //amount of QUEUE_DESC.EleBytes storage used by datatype // USHRT dType :3; //supports 8 possible data types (0-7) // USHRT dFoo :5; //unused bits of the unsigned short host's storage // } DATA_DESC; // This descriptor gives a home to all the housekeeping variables typedef struct { UCHAR *pBuffer; // pointer to storage, 16 to 4096 elements ULONG Tail :BITS_ELE_KNT; // # elements, with range of 0-4095 ULONG Head :BITS_ELE_KNT; // # elements, with range of 0-4095 ULONG EleBytes :8; // sizeof(elements) with range of 0-256 bytes // some unused bits will be left over if BITS_ELE_KNT < 12 USHRT EleKnt :BITS_ELE_KNT +1;// 1 extra bit for # elements (1-4096) //USHRT Flags :(8*sizeof(USHRT) - BITS_ELE_KNT +1); // flags you can use USHRT IsFull :1; // queue is full USHRT IsEmpty :1; // queue is empty USHRT Unused :1; // 16th bit of USHRT } QUEUE_DESC; // --------------------------------------------------------------------------- // Function prototypes QUEUE_DESC *Q_Init(QUEUE_DESC *Q, int BitsForEleKnt, int DataTypeSz); int Q_Put(QUEUE_DESC *Q, UCHAR *pNew); int Q_Get(UCHAR *pOld, QUEUE_DESC *Q); // --------------------------------------------------------------------------- QUEUE_DESC *Q_Init(QUEUE_DESC *Q, int BitsForEleKnt, int DataTypeSz) { memset((void *)Q, 0, sizeof(QUEUE_DESC));//init flags and bit integers to zero //select buffer size from powers of 2 to receive modulo // arithmetic benefit of bit uints overflowing Q->EleKnt = (USHRT)pow(2.0, BitsForEleKnt); Q->EleBytes = DataTypeSz; // how much storage for each element? // Randomly generated head, tail a test fixture only. // Demonstrates that the queue can be entered at a random point // and still perform properly. Normally zero srand(unsigned(time(NULL))); // seed random number generator with current time Q->Head = Q->Tail = rand(); // supposed to be set to zero here, or by memset Q->Head = Q->Tail = 0; // allocate queue's storage if(NULL == (Q->pBuffer = (UCHAR *)calloc(Q->EleKnt, Q->EleBytes))) { return NULL; } else { return Q; } } // --------------------------------------------------------------------------- int Q_Put(QUEUE_DESC *Q, UCHAR *pNew) { memcpy(Q->pBuffer + (Q->Tail * Q->EleBytes), pNew, Q->EleBytes); if(Q->Tail == (Q->Head + Q->EleKnt)) { // Q->IsFull = 1; Q->Tail += 1; return QUEUE_FULL_FLAG; // queue is full } Q->Tail += 1; // the unsigned bit int MUST wrap around, just like modulo return QUEUE_OK; // No errors } // --------------------------------------------------------------------------- int Q_Get(UCHAR *pOld, QUEUE_DESC *Q) { memcpy(pOld, Q->pBuffer + (Q->Head * Q->EleBytes), Q->EleBytes); Q->Head += 1; // the bit int MUST wrap around, just like modulo if(Q->Head == Q->Tail) { // Q->IsEmpty = 1; return QUEUE_EMPTY_FLAG; // queue Empty - nothing to get } return QUEUE_OK; // No errors } // // --------------------------------------------------------------------------- int _tmain(int argc, _TCHAR* argv[]) { // constrain buffer size to some power of 2 to force faux modulo arithmetic int LoopKnt = 1000000; // for benchmarking purposes only int k, i=0, Qview=0; time_t start; QUEUE_DESC Queue, *Q; if(NULL == (Q = Q_Init(&Queue, BITS_ELE_KNT, sizeof(int)))) { printf("\nProgram failed to initialize. Aborting.\n\n"); return 0; } start = clock(); for(k=0; k<LoopKnt; k++) { //printf("\n\n Fill'er up please...\n"); //Q->Head = Q->Tail = rand(); for(i=1; i<= Q->EleKnt; i++) { Qview = i*i; if(QUEUE_FULL_FLAG == Q_Put(Q, (UCHAR *)&Qview)) { //printf("\nQueue is full at %i \n", i); //printf("\nQueue value of %i should be %i squared", Qview, i); break; } //printf("\nQueue value of %i should be %i squared", Qview, i); } // Get data from queue until completely drained (empty) // //printf("\n\n Step into the lab, and see what's on the slab... \n"); Qview = 0; for(i=1; i; i++) { if(QUEUE_EMPTY_FLAG == Q_Get((UCHAR *)&Qview, Q)) { //printf("\nQueue value of %i should be %i squared", Qview, i); //printf("\nQueue is empty at %i", i); break; } //printf("\nQueue value of %i should be %i squared", Qview, i); } //printf("\nQueue head value is %i, tail is %i\n", Q->Head, Q->Tail); } printf("\nQueue time was %5.3f to fill & drain %i element queue %i times \n", (dbl)(clock()-start)/(dbl)CLOCKS_PER_SEC,Q->EleKnt, LoopKnt); printf("\nQueue head value is %i, tail is %i\n", Q->Head, Q->Tail); getchar(); return 0; }
quelle
Q->Tail == (Q->Head + Q->EleKnt)
in IhrerQ_Put
Methode niemals true zurückgeben, daQ->Head + Q->EleKnt
es sich nicht um eine Modulo-Addition handelt, was bedeutet, dass Sie den Kopf beim nächsten Schreiben einfach überschreiben. WennEleKnt
ja4096
, ist das ein Wert, den SieTail
niemals erreichen werden. Als nächstes würde die Verwendung als Warteschlange für Produzenten / Konsumenten Chaos anrichten, da IhrQ_Put
"zuerst schreibt, später Fragen stellt" und das aktualisiert,Tail
selbst wenn Sie feststellen, dass die Warteschlange voll ist. Der nächste Anruf beiQ_Put
überschreibt einfach den Kopf, als wäre nie etwas passiert.Put
sobald er feststellt, dass es voll ist, während Sie dennoch die Daten kopieren und dann die Überprüfung durchführen.Hier ist eine einfache Lösung in C. Angenommen, Interrupts sind für jede Funktion deaktiviert. Kein Polymorphismus & Zeug, nur gesunder Menschenverstand.
#define BUFSIZE 128 char buf[BUFSIZE]; char *pIn, *pOut, *pEnd; char full; // init void buf_init() { pIn = pOut = buf; // init to any slot in buffer pEnd = &buf[BUFSIZE]; // past last valid slot in buffer full = 0; // buffer is empty } // add char 'c' to buffer int buf_put(char c) { if (pIn == pOut && full) return 0; // buffer overrun *pIn++ = c; // insert c into buffer if (pIn >= pEnd) // end of circular buffer? pIn = buf; // wrap around if (pIn == pOut) // did we run into the output ptr? full = 1; // can't add any more data into buffer return 1; // all OK } // get a char from circular buffer int buf_get(char *pc) { if (pIn == pOut && !full) return 0; // buffer empty FAIL *pc = *pOut++; // pick up next char to be returned if (pOut >= pEnd) // end of circular buffer? pOut = buf; // wrap around full = 0; // there is at least 1 slot return 1; // *pc has the data to be returned }
quelle
Eine einfache Implementierung könnte bestehen aus:
Jedes Mal, wenn Sie Daten schreiben, bewegen Sie den Schreibzeiger vor und erhöhen den Zähler. Wenn Sie Daten lesen, erhöhen Sie den Lesezeiger und verringern den Zähler. Wenn einer der Zeiger n erreicht, setzen Sie ihn auf Null.
Sie können nicht schreiben, wenn counter = n. Sie können nicht lesen, wenn Zähler = 0 ist.
quelle
Einfacher Ringpuffer im C-Stil für ganze Zahlen. Verwenden Sie zuerst init als put und get. Wenn der Puffer keine Daten enthält, gibt er "0" Null zurück.
//===================================== // ring buffer address based //===================================== #define cRingBufCount 512 int sRingBuf[cRingBufCount]; // Ring Buffer int sRingBufPut; // Input index address int sRingBufGet; // Output index address Bool sRingOverWrite; void GetRingBufCount(void) { int r; ` r= sRingBufPut - sRingBufGet; if ( r < cRingBufCount ) r+= cRingBufCount; return r; } void InitRingBuffer(void) { sRingBufPut= 0; sRingBufGet= 0; } void PutRingBuffer(int d) { sRingBuffer[sRingBufPut]= d; if (sRingBufPut==sRingBufGet)// both address are like ziro { sRingBufPut= IncRingBufferPointer(sRingBufPut); sRingBufGet= IncRingBufferPointer(sRingBufGet); } else //Put over write a data { sRingBufPut= IncRingBufferPointer(sRingBufPut); if (sRingBufPut==sRingBufGet) { sRingOverWrite= Ture; sRingBufGet= IncRingBufferPointer(sRingBufGet); } } } int GetRingBuffer(void) { int r; if (sRingBufGet==sRingBufPut) return 0; r= sRingBuf[sRingBufGet]; sRingBufGet= IncRingBufferPointer(sRingBufGet); sRingOverWrite=False; return r; } int IncRingBufferPointer(int a) { a+= 1; if (a>= cRingBufCount) a= 0; return a; }
quelle
Ich denke, dass die Erweiterung der adam-rosenfield-Lösung für das Multithread-Szenario mit einem einzigen Produzenten und einem einzigen Konsumenten funktionieren wird.
int cb_push_back(circular_buffer *cb, const void *item) { void *new_head = (char *)cb->head + cb->sz; if (new_head == cb>buffer_end) { new_head = cb->buffer; } if (new_head == cb->tail) { return 1; } memcpy(cb->head, item, cb->sz); cb->head = new_head; return 0; } int cb_pop_front(circular_buffer *cb, void *item) { void *new_tail = cb->tail + cb->sz; if (cb->head == cb->tail) { return 1; } memcpy(item, cb->tail, cb->sz); if (new_tail == cb->buffer_end) { new_tail = cb->buffer; } cb->tail = new_tail; return 0; }
quelle
Die Lösung von @Adam Rosenfield könnte, obwohl sie korrekt ist, mit einer leichteren
circular_buffer
Struktur implementiert werden, die nichtcount
undcapacity
.Die Struktur konnte nur die folgenden 4 Zeiger enthalten:
buffer
: Zeigt auf den Start des Puffers im Speicher.buffer_end
: Zeigt auf das Ende des Puffers im Speicher.head
: Zeigt auf das Ende der gespeicherten Daten.tail
: Zeigt auf den Beginn der gespeicherten Daten.Wir könnten das
sz
Attribut beibehalten, um die Parametrisierung der Speichereinheit zu ermöglichen.Sowohl die
count
als auch diecapacity
Werte sollten unter Verwendung der obigen Zeiger ableitbar sein.Kapazität
capacity
ist einfach, da es durch Teilen des Abstands zwischen dembuffer_end
Zeiger und dembuffer
Zeiger durch die Speichereinheit abgeleitet werden kannsz
(das folgende Snippet ist Pseudocode):Anzahl
Für die Zählung werden die Dinge jedoch etwas komplizierter. Zum Beispiel gibt es keine Möglichkeit festzustellen, ob der Puffer leer oder voll ist, im Szenario
head
undtail
auf denselben Speicherort zeigend.Um dies zu beheben, sollte der Puffer Speicher für ein zusätzliches Element zuweisen. Wenn zum Beispiel die gewünschte Kapazität unseres Ringpuffers ist
10 * sz
, müssen wir zuweisen11 * sz
.Die Kapazitätsformel wird dann (Snippet unten ist Pseudocode):
Diese zusätzliche Elementsemantik ermöglicht es uns, Bedingungen zu konstruieren, die bewerten, ob der Puffer leer oder voll ist.
Leere Zustandsbedingungen
Damit der Puffer leer ist, zeigt der
head
Zeiger auf dieselbe Position wie dertail
Zeiger:Wenn das oben Gesagte als wahr ausgewertet wird, ist der Puffer leer.
Volle Zustandsbedingungen
Damit der Puffer voll ist, sollte der
head
Zeiger 1 Element hinter demtail
Zeiger sein. Daher sollte der Raum, der zum Abspringen vonhead
Ort zutail
Ort benötigt wird, gleich sein1 * sz
.wenn
tail
größer ist alshead
:Wenn das oben Gesagte als wahr ausgewertet wird, ist der Puffer voll.
wenn
head
größer ist alstail
:buffer_end - head
Gibt das Leerzeichen zurück, um vomhead
zum Ende des Puffers zu springen .tail - buffer
Gibt den Platz zurück, der benötigt wird, um vom Anfang des Puffers zum `Tail zu springen.head
zurtail
1 * sz
Wenn das oben Gesagte als wahr ausgewertet wird, ist der Puffer voll.
In der Praxis
Ändern von @Adam Rosenfield's, um die obige
circular_buffer
Struktur zu verwenden:#include <string.h> #define CB_SUCCESS 0 /* CB operation was successful */ #define CB_MEMORY_ERROR 1 /* Failed to allocate memory */ #define CB_OVERFLOW_ERROR 2 /* CB is full. Cannot push more items. */ #define CB_EMPTY_ERROR 3 /* CB is empty. Cannot pop more items. */ typedef struct circular_buffer { void *buffer; void *buffer_end; size_t sz; void *head; void *tail; } circular_buffer; int cb_init(circular_buffer *cb, size_t capacity, size_t sz) { const int incremented_capacity = capacity + 1; // Add extra element to evaluate count cb->buffer = malloc(incremented_capacity * sz); if (cb->buffer == NULL) return CB_MEMORY_ERROR; cb->buffer_end = (char *)cb->buffer + incremented_capacity * sz; cb->sz = sz; cb->head = cb->buffer; cb->tail = cb->buffer; return CB_SUCCESS; } int cb_free(circular_buffer *cb) { free(cb->buffer); return CB_SUCCESS; } const int _cb_length(circular_buffer *cb) { return (char *)cb->buffer_end - (char *)cb->buffer; } int cb_push_back(circular_buffer *cb, const void *item) { const int buffer_length = _cb_length(cb); const int capacity_length = buffer_length - cb->sz; if ((char *)cb->tail - (char *)cb->head == cb->sz || (char *)cb->head - (char *)cb->tail == capacity_length) return CB_OVERFLOW_ERROR; memcpy(cb->head, item, cb->sz); cb->head = (char*)cb->head + cb->sz; if(cb->head == cb->buffer_end) cb->head = cb->buffer; return CB_SUCCESS; } int cb_pop_front(circular_buffer *cb, void *item) { if (cb->head == cb->tail) return CB_EMPTY_ERROR; memcpy(item, cb->tail, cb->sz); cb->tail = (char*)cb->tail + cb->sz; if(cb->tail == cb->buffer_end) cb->tail = cb->buffer; return CB_SUCCESS; }
quelle