mikrocontroller.net

Forum: Compiler & IDEs #define oder wie geht man am besten vor?


Autor: Be Bo (bebo)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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:
#define xmDeclareRingBuffer(name, size, type) \
#if (6 > 0xFF) \
typedef unsigned short name##_indextype\
#else \
typedef unsigned char name##_indextype \
\
typedef {\
  type buffer[size];\
  name##_indextype read_offset, write_offset, free\
} 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.

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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?

Autor: Be Bo (bebo)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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?

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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...

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Sven P. (haku) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Peter Dannegger schrieb:
> Was soll denn wcscpy() sein?
> Ist ja keine C-Standardfunktion.
ISO-C99, Abschnitt 7.24.4.2.1.

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Sven P. (haku) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
POSIX:
http://www.opengroup.org/onlinepubs/009695399/func...

ISO gibts nur als PDF:
http://www.open-std.org/JTC1/SC22/WG14/www/standards
Dort dann 'WG14 N1124' verfolgen, im PDF auf Seite 376 (Seite 388 
absolut).

Ist für Breitzeichen und quasi nutzlos, da Breitzeichen nicht unbedingt 
den vollen Umfang von Unicode aufnehmen können.

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Rolf Magnus (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> 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?
template<typename T> T* copy(T* dest, const T* src)
{
}

template<> char* copy(char* dest, const char* src)
{
    return strcpy(dest, src);
}

template<> wchar_t* copy(wchar_t* dest, const wchar_t* src)
{
    return wcscpy(dest, src);
}

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:
#include <climits>
#include <iostream>

template<size_t Size>
struct SizeHelper { typedef typename SizeHelper<Size-1>::type type; };

template<> struct SizeHelper<0> { typedef unsigned char type; };
template<> struct SizeHelper<UCHAR_MAX+1> { typedef unsigned short type; };

template<size_t Size>
class RingBuffer
{
public:
    typedef typename SizeHelper<Size>::type IndexType;

    // ...

};

int main()
{
    std::cout << "Size of index for a 100 element buffer: "
              << sizeof(RingBuffer<100>::IndexType) << '\n'
              << "Size of index for a 10000 element buffer: "
              << sizeof(RingBuffer<10000>::IndexType) << '\n';
}

Autor: A. K. (prx)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Als Makroprozessor hat der C-Präprozessor seine Grenzen. Weit 
leistungsfähiger ist der M4, den man vor den C-Compiler hängen kann.

Autor: A. K. (prx)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Be Bo (bebo)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Be Bo (bebo)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.