Hallo,
ich würde mir gerne einen universellen aber optimierten Ringbuffer für C
(nicht C++) "bauen".
Unter optimiert verstehe ich, daß z.B. eine Indexvariable in
Abhängigkeit von der Buffergröße als unsigned char oder unsigned short
gewählt wird.
Darüber hinaus soll der Ringbuffer nicht dynamisch erzeugt werden, also
ohne malloc oder so.
Die Erste Idee war:
1
#define xmDeclareRingBuffer(name, size, type) \
2
#if (6 > 0xFF) \
3
typedef unsigned short name##_indextype\
4
#else \
5
typedef unsigned char name##_indextype \
6
\
7
typedef {\
8
type buffer[size];\
9
name##_indextype read_offset, write_offset, free\
10
} name; \
Nur ist es wohl nicht möglich Pre-Prozessor Anweisungen (#define, #if,
...) in #defines unterzubringen.
Gibt es hier eine andere Möglichkeit innerhalb des Macros einen Datentyp
in Abhängigkeit von einer Variablen zu bestimmen?
Oder wie würde man eine universelle Ring-Buffer Bibliothek
programmieren? Wenn ich universel sage, meine ich, daß die
Bufferstruktur + Funktionen in Abhängigkeit von Parametern generiert
werden.
Ich würde dann gern später schreiben:
mDeclareBuffer(InputBuffer, 64, unsigned char);
mDeclareBuffer(OutputBuffer, 512, unsigned short);
Dabei sollten dann structs erzeugt werden bei denen dann z.B.:
InputBuffer.free als unsigned char erzeugt wird, wobei OutputBuffer.free
als unsigned short declariert wird, eben in Abhängigkeit davon, was
gebraucht wird.
Hm, so wie du es vorhast, wird es mit dem C-PP nicht gehen.
Der Fall schreit geradezu nach templates, aber C++ willst du
ja explizit nicht (trotzdem der Versuch: man kann C++ auch auf einem
MC sinnvoll nutzen, ohne den Overhead von virtuellen Funktionen etc.
zu haben. Ich würde es mit C++ machen..).
Also musst du entweder ziemlich Klimmzüge mit dem PP machen
und trotzdem nicht alles erreichen (unterschiedlicher Code je nach
Typ z.B. wird schwierig), oder einen anderen PP verwenden.
Es gibt z.B. dem m4. So einen PP könnte man nutzen, um anhand
irgendwelcher Parameter C-Code zu generieren.
Falls man ohnehin make oder ähnliches nutzt, kann man dort ja
leicht so einen vorgeschalteten Schritt einbauen.
noch etwas anders:
Falls du vorhast, mehrere verschiedene Ringpuffer in einem
Programm zu verwenden (nur dann wird das Makro ja Sinn machen),
würde ich erstens die Anzahl der Elemente noch in die struct
mit aufnehmen und zweitens zwei Makros draus machen.
Das eine definiert die Funktionen zum Einfügen etc., und das
andere dann die struct für den konkreten Puffer.
Ansonsten hättest du für die drei Puffer mit (xy,512,char),
(abc,1024,char) und (def,10,char) die Funktionen dreimal
definiert. Durch Trennen der Funktionsdefinitionen und der
eigentlichen Puffer kann zum Einen ein Funktionssatz gleich alle
Puffer bedienen, falls es sich um den gleichen Grunddatentyp
handelt (spart Platz), und zum Anderen kann man dadurch
bei Bedarf den Puffer auch lokal halten, falls man das möchte.
Bei einem Makro für alles müsste der Puffer prinzipiell global
sein.
Dann fällt mir noch eine Variante ein (die aber nur Sinn macht,
wenn memcpy() nicht als Funktion aufgerufen wird, sondern vom
Compiler gleich inline expandiert wird, was bei avr-gcc meines
Wissens der Fall ist):
Die Funktionen zum Einfügen etc. könnte man gleich generisch
bauen, sodaß sie gar nicht wissen müssen, mit welchem Datentyp
sie hantieren und dafür nur Zeiger auf den Anfang von Elementen
kennen als void* und die Elementgröße als Parameter bekommen,
ähnlich wie bsearch() und qsort() aus der Standardbibliothek.
Dann definiert man sie nur einmal für alle Puffer, die jemals
kommen werden. Das spart enorm Code (je nach Anzahl der Puffer),
kostet etwas mehr Rechenzeit (wird wenige % sein) und vereinfacht
dein Makro deutlich.
Habe ich schon erwähnt, daß ich es in C++ machen würde?
Hi,
C++ würde mir ja auch gefallen, aber der C32(Microchip) kann kein C++.
Die frage war auch weniger darauf gerichtet irgendeinen Datentyp
verarbeiten zu können, als vielmehr bedingt Programmcode zu generieren,
wie es ja im Grunde mit #if teilweise möglich ist. Leider nicht in dem
Umfang, wie ich mir das wünsche.
Im Ügrigen: Mir ist nicht bekannt, daß C++ in der Lage wäre das Problem
zu lösen. Templates können zwar unterschiedliche Datentypen verarbeiten,
aber in Abhängigkeit von Parametern sich für unterschiedlichen Code zu
entscheiden geht damit soweit ich weiß auch nicht.
Oder ist es mit C++ möglich (in einem Template), in Abhängigkeit von
einem Typ, zwischen der Nutzung von strcpy(), wcscpy(), ... zu
unterscheiden?
notfalls mit Spezialisierung von templates
Was hier letztlich Sinn macht, hängt natürlich davon ab, was du
eigentlich vorhast?
Viele Ringpuffer in einem Program? Oder nur wenige, und den
Puffer so bauen, daß er bis zu deinem Lebensende immer wieder
in anderen Programmen genutzt werden kann? Geht es eher darum,
Programmspeicher (Code) zu sparen oder mehr um den letzten Rest
Rechenleistung?
Alles auf einmal wird mit C nicht zu 100% gehen...
Be Bo schrieb:
> Oder ist es mit C++ möglich (in einem Template), in Abhängigkeit von> einem Typ, zwischen der Nutzung von strcpy(), wcscpy(), ... zu> unterscheiden?
Was soll denn wcscpy() sein?
Ist ja keine C-Standardfunktion.
Peter
Es kostet nur unnötig Rechenzeit, immer eine Variable "free" zu
aktualisieren.
Will man wissen, ob was im Puffer ist, reicht es, einfach "read_offset"
mit "write_offset" zu vergleichen.
Damit wird nur dann gerechnet, wenn man es wirklich benötigt und nicht
jedesmal in den Schreib- und Lesefunktionen.
Zusätzlich macht das "free" Atomic-Probleme bei ner FIFO in Interrupts,
da es ja vom Main und vom Interrupt geändert werden muß.
Peter
Sven P. schrieb:
> Peter Dannegger schrieb:>> Was soll denn wcscpy() sein?>> Ist ja keine C-Standardfunktion.> ISO-C99, Abschnitt 7.24.4.2.1.
Hast Du dazu auch nen Link?
Der AVR-GCC und der Keil C51 kennen es jedenfalls nicht.
Peter
P.S.:
Aha, ist für 16Bit-Zeichensatz.
Das hat dann aber überhaupt nichts mit der FIFO-Größe zu tun.
Ich würde in ner FIFO generell keine Stringfunktionen verwenden.
Eine FIFO kann ja auch Binärdaten beinhalten, d.h. auch das Zeichen '\0'
ist erlaubt.
Peter
> Im Ügrigen: Mir ist nicht bekannt, daß C++ in der Lage wäre das Problem> zu lösen. Templates können zwar unterschiedliche Datentypen verarbeiten,> aber in Abhängigkeit von Parametern sich für unterschiedlichen Code zu> entscheiden geht damit soweit ich weiß auch nicht.
Doch, mit Template-Spezialisierung.
> Oder ist es mit C++ möglich (in einem Template), in Abhängigkeit von> einem Typ, zwischen der Nutzung von strcpy(), wcscpy(), ... zu> unterscheiden?
Meinst du sowas?
Beim Ringpuffer könnte man eine automatisierte Erkennung leicht mit
Template-Metaprogramming machen. Allerdings braucht g++ bei langen
rekursiven template-Instanziierungen relativ lange. Außerdem muß man per
Kommandozeile die maximale Verschachtelungstiefe von Templates
hochsetzen. Gehen tut's aber. Hier mal ein Beispiel für den Ringpuffer:
Be Bo schrieb:
> zu lösen. Templates können zwar unterschiedliche Datentypen verarbeiten,> aber in Abhängigkeit von Parametern sich für unterschiedlichen Code zu> entscheiden geht damit soweit ich weiß auch nicht.
Solange dieser Parameter in Form normaler C++ Abfragen im
Codeüberprüfbar ist geht das auch ohne Spezialisierung von Templates.
Auch Typabfragen sind dabei möglich, mindestens jedenfalls für
Klassentypen.
Das mit dem wcscpy() wird z.B. unter Windows für Wide-Charakter Strings
(16 bit = 1 Zeichen; wchar_t anstatt char )eingesetzt. Sollte jetzt aber
nichts mit dem Ringbuffer zu tun haben.
Nun ja, da ich aber den C32 verwenden will/muß, kann ich C++ nicht
nutzten.
@Peter:
> Es kostet nur unnötig Rechenzeit, immer eine Variable "free" zu> aktualisieren.
Nicht unbedingt. Wenn ich Daten in eine MessageQueue einfüge,
interessiert mich vorher, ob genug Speicher für die Nachricht zur
Verfügung steht. Will ich bspw. 10 Zeichen einfügen, kann die Funktion,
die nach freiem Speicher schaut, diesen auch gleich reservieren, allso
free-=10; rechnen. Dann kann die andere Funktion nur noch die Daten
schreiben, ohne sich um die Aktualisierung zu kümmern.
Wenn
> Will man wissen, ob was im Puffer ist, reicht es, einfach "read_offset"> mit "write_offset" zu vergleichen.
Woher weiß ich, ob write_offset==read_offset ein leerer oder randvoller
FIFO ist?
Im übrigen hat die Sache mit dem Reservieren den Vorteil, daß ich den
write_offset damit bereits weiterschiebe. D.h., wenn ein Interrupt
auslößt, kann dieser munter in den FIFO schreiben, ohne sich darum zu
kümmern, daß das Hauptprogramm das auch gerade tut. Anderen Falls müßte
das Hauptprogram Interrupts sperren, bis es die Nachricht in die
MessageQueue eingefügt hat. Durch die Reservierung ist das nicht nötig.
> Zusätzlich macht das "free" Atomic-Probleme bei ner FIFO in Interrupts,> da es ja vom Main und vom Interrupt geändert werden muß.
Natürlich muß die Reservierungsroutine kurzzeitig Interrupts sperren,
aber eben nur für die Zeit bis free und write_offset angepasst sind.
Be Bo schrieb:
> Natürlich muß die Reservierungsroutine kurzzeitig Interrupts sperren,> aber eben nur für die Zeit bis free und write_offset angepasst sind.
Nö.
Einen nur Lese- oder Schreibzugriff auf 8 oder 16Bit sollte ein
32-Bitter atomar hinkriegen.
Nur der Read-Modify-Write Zugriff auf "free" muß atomar gekapselt
werden.
Der Vergleich "write_offset" - "read_offset" liefert auch die Größe des
belegten FIFO (Überlauf beachten).
Peter
Beim PIC32 werden Additionen in Prozessorregistern ausgeführt. D.h., daß
es passieren könnte, daß gerade in diesem Augenblick ein Interrupt
ebenfalls eine Message einfügt. Danach würde der write_offset nicht mehr
stimmen. Die PIC24 bspw. können zwar direkt etwas zu einer
Speicherstelle hinzuaddieren, aber das Problem wäre dann immer noch, daß
die Funktion dem Hauptprogramm ja einen Offset zurückliefern muß, damit
dieses weiß, ab wo es die Daten einfügen darf. Hier müßte anschließend
also der write_offset noch einmal gelesen werden. Wie man es dreht und
wendet, aber ich sehe hier keine Möglichkeit dieses mit enableten
Interrupts zu tun. Einzig zwischen dem Erniedrigen von free und dem
Erhöhen von write_offset könnte man die Interrupts wieder kurz enablen.
> Der Vergleich "write_offset" - "read_offset" liefert auch die Größe> des belegten FIFO (Überlauf beachten).
Ja, aber wenn write_offset = 13 und read_offset = 13 ist das Ergebniss
0. Ist der Buffer den nun leer, oder ist write_offset nur deswegen auf
13, weil die MessageQueue bis zum letzten Byte gefüllt ist. Ich meine,
habe ich einen 64 Bytes großen Buffer und füge ich 64 Bytes ein,
incrementiere ich write_offset unter Beachtung des Überlaufs 64 mal.
Damit ist write_offset nach dem einfügen genau so groß wie zuvor, nur
der Buffer ist nicht mehr leer sonder randvoll. Deine Rechnung würde nur
funktionnieren, wenn ich immer mindestens 1 Byte im Buffer leer lasse.
Was man natürlich auch machen könnte.
Be Bo schrieb:
> Beim PIC32 werden Additionen in Prozessorregistern ausgeführt.
Das ist üblich, machen viele CPUs so, z.B. der AVR.
> D.h., daß> es passieren könnte, daß gerade in diesem Augenblick ein Interrupt> ebenfalls eine Message einfügt. Danach würde der write_offset nicht mehr> stimmen.
Klar stimmt der.
Nicht die Addition, sondern nur das Lesen/Schreiben der Variablen muß
atomar sein.
Schau mal in mein Beispiel:
Beitrag "AVR-GCC: UART mit FIFO"> Deine Rechnung würde nur> funktionnieren, wenn ich immer mindestens 1 Byte im Buffer leer lasse.> Was man natürlich auch machen könnte.
Genau so isses.
Peter