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


von Be B. (bebo)


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:
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.

von Klaus W. (mfgkw)


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.

von Klaus W. (mfgkw)


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.

von Klaus W. (mfgkw)


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?

von Be B. (bebo)


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?

von Klaus W. (mfgkw)


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...

von Peter D. (peda)


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

von Sven P. (Gast)


Lesenswert?

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

von Peter D. (peda)


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

von Peter D. (peda)


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.

von Sven P. (Gast)


Lesenswert?

POSIX:
http://www.opengroup.org/onlinepubs/009695399/functions/wcscpy.html

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.

von Peter D. (peda)


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

von Rolf Magnus (Gast)


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?
1
template<typename T> T* copy(T* dest, const T* src)
2
{
3
}
4
5
template<> char* copy(char* dest, const char* src)
6
{
7
    return strcpy(dest, src);
8
}
9
10
template<> wchar_t* copy(wchar_t* dest, const wchar_t* src)
11
{
12
    return wcscpy(dest, src);
13
}

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:
1
#include <climits>
2
#include <iostream>
3
4
template<size_t Size>
5
struct SizeHelper { typedef typename SizeHelper<Size-1>::type type; };
6
7
template<> struct SizeHelper<0> { typedef unsigned char type; };
8
template<> struct SizeHelper<UCHAR_MAX+1> { typedef unsigned short type; };
9
10
template<size_t Size>
11
class RingBuffer
12
{
13
public:
14
    typedef typename SizeHelper<Size>::type IndexType;
15
16
    // ...
17
18
};
19
20
int main()
21
{
22
    std::cout << "Size of index for a 100 element buffer: "
23
              << sizeof(RingBuffer<100>::IndexType) << '\n'
24
              << "Size of index for a 10000 element buffer: "
25
              << sizeof(RingBuffer<10000>::IndexType) << '\n';
26
}

von (prx) A. K. (prx)


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.

von (prx) A. K. (prx)


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.

von Be B. (bebo)


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.

von Peter D. (peda)


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

von Be B. (bebo)


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.

von Peter D. (peda)


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

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.