Forum: Mikrocontroller und Digitale Elektronik C: Statische "Member" Variablen zurückgeben oder gleich globale verwenden? OOP Ansätze in C gesucht


von Jan K. (jan_k)


Lesenswert?

Hi Leute.

Ich plane gerade eine Neuauflage eines alten Embedded Programms 
(STM32f103, uart, dma, adc, nix großes) unter der Berücksichtigung eines 
besseren Designs. Wesentlich sind für mich momentan die Datenkapselung 
und Modularisierung sowie der (sinnvolle) Verzicht auf globale 
Variablen, habe aber vermutlich einige Sachen noch nicht verstanden. Ich 
würde gerne 2 Beispiele hier auflisten und wissen wollen, ob man das so 
machen kann, oder ob das bereits grober Unfug ist.

Als erstes habe ich eine kleine Queue geschrieben:

fifo.h
1
...
2
typedef struct
3
{
4
    uint32_t overflows;
5
    uint32_t underflows;
6
}
7
fifoErrors_t;
8
9
typedef struct
10
{
11
    semaphore_t sem;       // # Zeichen im Puffer; Counting Semaphore
12
    void *buffer;     // Buffer Address
13
    void *pread;               // Lesezeiger
14
    void *pwrite;              // Schreibzeiger
15
    int32_t read2end, write2end;  // Verbleibende Zeichen bis zum Ende
16
    size_t bufferSize;                 // Puffer-Größe in byte
17
    size_t elementSize; // size of the base data types in bytes
18
    size_t numberOfElements;
19
    fifoErrors_t err;
20
}
21
fifo_t;
22
23
typedef enum
24
{
25
    FIFO_SUCCESS = 0,
26
    FIFO_OVERFLOW,
27
    FIFO_UNDERFLOW,
28
    FIFO_WRONG_SIZE,
29
    FIFO_SEMAPHORE_ERR
30
}
31
fifoErrorCodes_t;
32
33
// exported functions
34
extern fifoErrorCodes_t fifo_init( fifo_t *f, void *buffer, size_t bufferSize, size_t elementSize );
35
extern fifoErrorCodes_t fifo_put( fifo_t *f, const void *data );
36
extern fifoErrorCodes_t fifo_get( fifo_t *f, void *out );
37
extern fifoErrorCodes_t fifo_flush( fifo_t *f );
38
39
...

Ich deklariere also eine (exportierte) struct, die alle "member" 
Variablen enthält (im Moment noch public und private, sollte man die 
privaten eventuell in einer weiteren struct kapseln?). Alle Methoden 
haben als erstes Argument einen Zeiger auf meine struct.
Definiert werden meine Objekte nun in der Datei, in dessen Modul sie 
benötigt werden, z.B.

modul_braucht_fifo.c
1
...
2
#include "fifo.h"
3
...
4
    int16_t buffer_i[20];
5
    double buffer_d[20];
6
    int16_t testInt = -12, testIntReturn;
7
    double testDouble = 1.5125e-19, testDoubleReturn;
8
  
9
    fifo_init( &fifo_i, buffer_i, sizeof(buffer_i), sizeof(buffer_i[0]) );   
10
    fifo_init( &fifo_d, buffer_d, sizeof(buffer_d), sizeof(buffer_d[0]) );   
11
...
12
    fifo_put(&fifo_i, &testInt );
13
    fifo_put(&fifo_d, &testDouble );
14
...
15
    fifo_get(&fifo_i, &testIntReturn);
16
    fifo_get(&fifo_d, &testDoubleReturn);
17
...

Das funktioniert soweit auch. Ist diese Herangehensweise in Ordnung? Zum 
Beispiel muss ich ja hier den Puffer im caller erstellen. Sollte ich die 
fifos und die Puffer static deklarieren, damit sie nur in 
modul_braucht_fifo.c sichtbar sind?

Die static Geschichte führt mich zu meiner zweiten Frage. Angenommen ich 
habe ein Modul, das vermutlich nur ein Mal instanziiert wird, z.B. eine 
UART. Erstelle ich den Code Peripherie Init Code mit STMCubeMX, werden 
in der usart.c z.B. direkt folgende Variablen erstellt:

usart.c
1
#include "usart.h"
2
...
3
UART_HandleTypeDef huart1;
4
DMA_HandleTypeDef hdma_usart1_rx;
5
DMA_HandleTypeDef hdma_usart1_tx;
6
...
7
diverse Init Funtionen

In der Header Datei wird huart1 exportiert:
1
...
2
extern UART_HandleTypeDef huart1;
3
void MX_USART1_UART_Init( void );
4
...

Sollte ich möglicherweise auf die globale Variable verzichten, die 
Variablen in der c Datei static machen und per Funktion exportieren, zb. 
USART_GetHandle() [können/dürfen überhaupt Zeiger auf static Variablen 
erstellt/zurückgegeben werden?], oder macht es möglicherweise auch hier 
Sinn, das Handle vom caller zu erstellen und zu übergeben, etwa so wie 
im ersten Beispiel? Oder lässt man es einfach so und verwendet die 
globalen Variablen?

Vielen Dank für eure Antworten!

: Bearbeitet durch User
von Klaus W. (mfgkw)


Lesenswert?

Jan K. schrieb:
> Das funktioniert soweit auch. Ist diese Herangehensweise in Ordnung? Zum
> Beispiel muss ich ja hier den Puffer im caller erstellen. Sollte ich die
> fifos und die Puffer static deklarieren, damit sie nur in
> modul_braucht_fifo.c sichtbar sind?

Alle globalen Variablen, die du nur in einem Modul brauchst, sind 
sinnvollerweise static.

Jan K. schrieb:
> Ich deklariere also eine (exportierte) struct, die alle "member"
> Variablen enthält (im Moment noch public und private, sollte man die
> privaten eventuell in einer weiteren struct kapseln?).

Wo siehst du in C public und private?

Jan K. schrieb:
> können/dürfen überhaupt Zeiger auf static Variablen
> erstellt/zurückgegeben werden?

ja, vollkommen legitim.

Jan K. schrieb:
> Sollte ich möglicherweise auf die globale Variable verzichten, die
> Variablen in der c Datei static machen und per Funktion exportieren, zb.
> USART_GetHandle() [können/dürfen überhaupt Zeiger auf static Variablen
> erstellt/zurückgegeben werden?], oder macht es möglicherweise auch hier
> Sinn, das Handle vom caller zu erstellen und zu übergeben, etwa so wie
> im ersten Beispiel?


Im Prinzip geht beides.
Mit Handle ist halt unnötig umständlich, wenn man sicher nur eines davon 
braucht.

Wenn man es mit einem Handle macht, kann das ja direkt ein Zeiger sein.

Jan K. schrieb:
> Oder lässt man es einfach so und verwendet die
> globalen Variablen?

Ich mag globale Variablen prinzipiell nicht, auch wenn nichts 
technisches dagegen spricht.

: Bearbeitet durch User
von Jan K. (jan_k)


Lesenswert?

Vielen Dank für deine Antwort!

Klaus W. schrieb:
> Jan K. schrieb:
>> Das funktioniert soweit auch. Ist diese Herangehensweise in Ordnung? Zum
>> Beispiel muss ich ja hier den Puffer im caller erstellen. Sollte ich die
>> fifos und die Puffer static deklarieren, damit sie nur in
>> modul_braucht_fifo.c sichtbar sind?
>
> Alle globalen Variablen, die du nur in einem Modul brauchst, sind
> sinnvollerweise static.

> Wo siehst du in C public und private?

"private" sind für mich Variablen, die in der .c eines Modules definiert 
wurden und ggf. static sind, auf jeden Fall jedoch nicht mit extern 
versehen werden oder den Weg in einen Header finden. Du hast sie oben 
jetzt globale Variablen genannt. Oder halt Variablen in einer 
verschachtelten Struct, hatte hier noch sowas gefunden: 
http://stackoverflow.com/a/15435780/3620376

> Jan K. schrieb:
>> können/dürfen überhaupt Zeiger auf static Variablen
>> erstellt/zurückgegeben werden?
>
> ja, vollkommen legitim.
>

oki

> Jan K. schrieb:
>> Sollte ich möglicherweise auf die globale Variable verzichten, die
>> Variablen in der c Datei static machen und per Funktion exportieren, zb.
>> USART_GetHandle() [können/dürfen überhaupt Zeiger auf static Variablen
>> erstellt/zurückgegeben werden?], oder macht es möglicherweise auch hier
>> Sinn, das Handle vom caller zu erstellen und zu übergeben, etwa so wie
>> im ersten Beispiel?
>
>
> Im Prinzip geht beides.
> Mit Handle ist halt unnötig umständlich, wenn man sicher nur eines davon
> braucht.
>
> Wenn man es mit einem Handle macht, kann das ja direkt ein Zeiger sein.

Klar, die Handles werden per Zeiger durch die Gegend gereicht.

> Jan K. schrieb:
>> Oder lässt man es einfach so und verwendet die
>> globalen Variablen?
>
> Ich mag globale Variablen prinzipiell nicht, auch wenn nichts
> technisches dagegen spricht.

Okay.

von Klaus W. (mfgkw)


Lesenswert?

Jan K. schrieb:
>> Wenn man es mit einem Handle macht, kann das ja direkt ein Zeiger sein.
>
> Klar, die Handles werden per Zeiger durch die Gegend gereicht.

Ich meinte, daß das Handle gleich der Zeiger ist.
(Vielleicht meintest du dasselbe, bin mir aber nicht so sicher.)

von Yalu X. (yalu) (Moderator)


Lesenswert?

Jan K. schrieb:
> Ich deklariere also eine (exportierte) struct, die alle "member"
> Variablen enthält (im Moment noch public und private, sollte man die
> privaten eventuell in einer weiteren struct kapseln?).

Das kann man machen. Wenn man die Inhalte der Private-Struktur (nennen
wir sie fifo_private_t) wirklich verstecken will, bietet sich folgende
Vorgehensweise an:

fifo.h:
1
struct fifo_private_t; // unvollständige Deklaration reicht hier aus
2
3
typedef struct
4
{
5
  ...
6
  struct fifo_private_t *fifo_private;
7
}
8
fifo_t;

Der Aufbau von fifo_private_t bleibt dem Benutzer der FIFO verborgen und
ist in einem separaten Header-File deklariert:

fifo_private.h:
1
struct fifo_private_t  // vollständige Deklaration
2
{
3
  ...
4
};

Dieses Header-File ist wird nur innerhalb des Implementierungsmoduls der
FIFO verwendet.

Diese Vorgehensweise hat auch den Vorteil, dass modul_braucht_fifo.c
schneller kompiliert, da nur die öffentlichen Teile der FIFO inkludiert
werden müssen. In diesem Beispiel ist der Unterschied zwar gering, aber
bei größeren Projekten zieht manchmal ein einzelnes #include einen
ganzen Rattenschwanz an weiteren Dateien hinter sich her, der auf diese
Weise deutlich verkürzt werden kann.

Eine andere, pragmatische Möglichkeit wäre es, die "Private"-Variablen
in der Struktur fifo_t einfach mit einem Präfix zu versehen, der dem
Benutzer sagt "Diese Variable ist nur für den internen Gebrauch, bitte
nicht anfassen".

> Zum Beispiel muss ich ja hier den Puffer im caller erstellen. Sollte
> ich die fifos und die Puffer static deklarieren, damit sie nur in
> modul_braucht_fifo.c sichtbar sind?

Schöner fände ich es, wenn die Puffer ebenfalls gekapselt würden. Das
würde bedeuten, dass sie in fifo_init dynamisch erzeugt werden.

> Sollte ich möglicherweise auf die globale Variable verzichten, die
> Variablen in der c Datei static machen und per Funktion exportieren,
> zb. USART_GetHandle()

Der Hauptvorteil der Get-Funktion besteht in diesem Fall darin, dass man
die Variable nicht versehentlich überschreiben kann. Nachteilhaft ist,
dass sie nicht geinlinet werden kann und damit Overhead erzeugt (wobei
man sie i.Allg. nur einmal aufrufen wird, so dass dieser nicht ins
Gewicht fällt)

Besser wäre es allerdings, wenn huart1 gleich von MX_USART1_UART_Init
zurückgegeben würde, so dass man sich eine weitere Funktion (und damit
einen weiteren globalen Identifier) spart.

Noch eine kleine Anmerkung:

Bei der Funktion fifo_init würde ich die Puffergröße in Elementen und
nicht in Bytes übergeben, wie es bspw. auch die Bibliotheksfunktionen
fread, fwrite, calloc und qsort machen. Du ersparst dir damit auch
mögliche Probleme, wenn für bufferSize versehentlich ein Wert übergeben
wird, der kein Vielfaches von elementSize ist.

von sebastian (Gast)


Lesenswert?

Yalu X. schrieb:
> Wenn man die Inhalte der Private-Struktur (nennen
> wir sie fifo_private_t) wirklich verstecken will, bietet sich folgende
> Vorgehensweise an

Man könnte auch gleich alles verstecken (alles private), mit dem in 
#4253707 gezeigten Prinzip. Im öffentlichen Header ist dann nur noch die 
Forward-Deklarartion der FIFO Struktur, und alle Funktionsprototypen.
Dann kannst du die FIFO-Struktur in den Benutzer-Modulen nur noch per 
Zeiger weitergeben an die FIFO-Funktionen. Du musst also für alles, was 
du brauchst, entsprechende FIFO-Funktionen haben. Sollte aber nicht so 
viel sein (create, destroy, push_back, pop_front)

Das Problem, dass du die Struktur innerhalb des FIFO Moduls allokieren 
musst, hast du immer - egal, ob du alles oder nur einen Teil der member 
hinter einem Zeiger auf eine Struktur mit Forward Deklaration 
versteckst. Weils der benutzer halt leider nicht mehr kann. Er weiß ja 
nicht, wie groß die Struktur ist.
Wenn du nur einen FIFO hast, dann kannst du alles static allokieren und 
den Zeiger aus der Schnittstelle der Funktionen rausnehmen.
Ansonsten gehts auf dem Heap oder aus einem Pool. Oder du gibst der 
create Funktion einen Puffer mit, und die nimmt sich dann einen Teil 
davon für die Verwaltungsstruktur und den Rest für die Daten.

von Rolf M. (rmagnus)


Lesenswert?

sebastian schrieb:
> Man könnte auch gleich alles verstecken (alles private), mit dem in
> #4253707 gezeigten Prinzip.

Nächstes mal bitte die Formatierungsregeln beachten.
Beitrag "Re: C: Statische "Member" Variablen zurückgeben oder gleich globale verwenden? OOP Ansätze in C gesu"

> Im öffentlichen Header ist dann nur noch die Forward-Deklarartion der
> FIFO Struktur, und alle Funktionsprototypen.
> Dann kannst du die FIFO-Struktur in den Benutzer-Modulen nur noch per
> Zeiger weitergeben an die FIFO-Funktionen. Du musst also für alles, was
> du brauchst, entsprechende FIFO-Funktionen haben. Sollte aber nicht so
> viel sein (create, destroy, push_back, pop_front)

Sowas ist ja in C nicht unüblich. Siehe z.B. FILE*, der genau diese 
Vorgehensweise umsetzt.

von Stefan F. (Gast)


Lesenswert?

> sollte man die privaten eventuell in einer weiteren struct kapseln?

Ich würde das durch Namenskonvention lösen. Zum Beispiel alle privaten 
Member mit dem prefix "priv_" beginnen lassen und dann auch nur privat 
nutzen.

> Sollte ich möglicherweise auf die globale Variable verzichten

Ja, es sei denn, du willst mit genau einer Instanz des Objektes 
arbeiten.
Ansonsten würde die die das Objekt dort lokal erzeugen, wo es gebraucht 
wird. Oder notfalls auf dem heap, wenn es als Ergebnis einer Funktion 
zurückgeliefert werden soll.

> können/dürfen überhaupt Zeiger auf static Variablen erstellt/zurückgegeben 
werden.

Ja kann man.

Unabhängig davon solltest du vorsehen, mehrere Objekte vom gleichen Typ 
haben zu können. Es wäre blöd, wenn es nur einen Fifo geben kann.

Also als Konstruktor-Ersatz eine Methode, wie fifo_create(), welche 
einen Struktur auf dem heap (mit malloc) erzeugt und dann einen Pointer 
darauf zurück liefert.

Oder eine Funktion fifo_init() welcher als Argument ein Zeiger auf das 
Objekt übergeben wird, die initialisiert werden soll.


Mit Objekt meine ich hier: Eine Variable vom Typ der Struktur.

von W.S. (Gast)


Lesenswert?

Jan K. schrieb:
> Wesentlich sind für mich momentan die Datenkapselung
> und Modularisierung sowie der (sinnvolle) Verzicht auf globale
> Variablen,

Nanana.. Kipp das Kind nicht mit dem Bade aus.

Also: Globale Variablen, also solche, die von einem Modul (ich würde 
hier liebend gern von einem UNIT schreiben, wenn die C-Leute das 
verstehen würden) per Headerdatei exportiert werden, sind im Allgemeinen 
nötig, denn die Alternative wäre, eine Prozedur (function) zu 
exportieren, die so eine Variable in den Modul importiert und eine 
andere, die ihn wieder exportiert. Das ist albern und 'elektronische 
Wichserei'.

Viel sinnvoller ist tatsächlich eine sinnvolle Modularisierung. Ich sehe 
hier in diesem Forum häufig genug Quellen, wo alles durcheinander geht. 
Ein typisches Beispiel ist ein virtueller COM-Port auf Device-Seite, wo 
letztlich die auf dem USB vorhandene Blockstruktur zum Hauptprogramm 
exportiert wird. Dümmer geht's nicht. Also: Ein Peripherie-Treiber 
sollte die inneren Befindlichkeiten der betreffenden Peripherie in sich 
kapseln und sinnvoll behandeln und damit NICHT andere Programmteile 
belästigen. Sowas ist eine echte Art Kapselung, die auch was bringt. 
Irgend ein verquaaster Unit "fifo.c" bringt es nicht, sowas ist nur eine 
unnötige Verkomplizierung der Strukturen. Vergleiche doch mal:

// exported functions
extern fifoErrorCodes_t fifo_init( fifo_t *f, void *buffer, size_t 
bufferSize, size_t elementSize );
extern fifoErrorCodes_t fifo_put( fifo_t *f, const void *data );
extern fifoErrorCodes_t fifo_get( fifo_t *f, void *out );
extern fifoErrorCodes_t fifo_flush( fifo_t *f );

mit

#ifndef _USB_H_
#define _USB_H_
extern bool UsbRxAvail (void);    /* true, wenn Char's vom Host abholbar 
sind */
extern char UsbGetChar (void);    /* liest ein Char vom Host */
extern bool UsbTxReady (void);    /* true, wenn mindestens 1 Char 
gesendet werden kann */
extern bool UsbTxEmpty (void);    /* true, wenn der Sendepuffer total 
leer ist */
extern char UsbCharOut (char c);  /* sendet ein Char zum Host */
extern void UsbStrOut  (char* S); /* sendet einen String zum Host */
extern word UsbSetup   (void);    /* Starten des USB-Cores */
#endif

Bemerkst du den Unterschied?

W.S.

von Klaus W. (mfgkw)


Lesenswert?

Für das alles bietet übrigens C++ wesentlich mehr als C (z.B. 
namespaces).

Ich verstehe nicht ganz, warum z.B. mit Namenskonventionen mühsam etwas 
in C nachbaut, was man in C++ kostenlos haben kann.
Quelltext umnebenennen und nutzen!

Wenn man von C++ nicht mehr als das nehmen will, muß man ja nicht.
(Auch wenn es früher oder später auch für anderes sinnvoll ist, aber das 
ist ein anders Thema.)

von Stefan F. (Gast)


Lesenswert?

Manche Leute vermeiden C++ auf Mikrocontrollern wegen der dynamischen 
Speicherverwaltung und dem overhead durch Funktions-Zeiger.

Aber: Objekte müssen nicht auf dem Heap liegen. Man muss Objekte nicht 
mit new() erzuegen. Und Objekte müssen nicht zwangsläufig virtuelle 
(überschreibbare) Methoden haben. Nicht virtuelle Methoden werden auch 
nciht indirekt aufgerufen, sie erzeugen keinen nennenswerten Overhead.

von Carl D. (jcw2)


Lesenswert?

> Manche Leute vermeiden C++ auf Mikrocontrollern wegen der dynamischen
Speicherverwaltung und dem overhead durch Funktions-Zeiger.

Gerade die Funktionszeiger seh ich oben in der Struktur auch. In C++ 
gibt es die aber nur einmal, in der vtbl, auf die ein Zeiger am Anfang 
eines Objekts zeigt. Es ist also schön wenn man Dinge selber 
"nach-erfindet", aber dann, man hat ja nun verstanden, darf man auf das 
Original wechseln.
Nach einiger Zeit würde man "böse" templates ausprobieren, wo sich um 
Fragen wie Elementgröße, Queuelänge, ... als Parameter mit geben und den 
Compiler machen lassen. Wenns zu groß/zu langsam wird, dann wird 
optimiert.

von Klaus W. (mfgkw)


Lesenswert?

Stefan U. schrieb:
> Manche Leute vermeiden C++ auf Mikrocontrollern wegen der dynamischen
> Speicherverwaltung und dem overhead durch Funktions-Zeiger.

Wenn ich in einem C++-Quelltext nicht mehr reinschreibe, als auch bisher 
in der C-Datei steht, dann wird da auch kein Overhead dazu kommen.

Beispiel dynamischer Speicher: exakt wie C, solange man keine 
zusätzlichen C++-Möglichkeiten nutzt.

Beispiel vtable: existiert nicht, solange man nicht virtuelle Methoden 
und Ableitung hat.

"Manche Leute" sind wahrscheinlich die, die von Tuten und Blasen keine 
Ahnung haben.

von DerDan (Gast)


Lesenswert?

Gerade virtuelle Funktionen sind aber sehr sinnvoll...

sie vermeiden die Spezialisierung an zentraler Stelle.
Modular zu programmieren geht ohne virtuelle Funktionen quasi nicht.

Damit ist klar dass ohne Overhead keine Modularität zu erreichen ist.
Man bekommt halt nichts geschenkt.

In Sinne von Overhead vermeiden musste man auch schon auf Unterprogramme 
vermeiden was einem dazu bringt noch nicht mal strukturiert zu 
programmieren.
Und das will hoffentlich niemand

Der TE frägt aber eher in Richtung sauber programmieren als in Richtung 
geringster Overhead.

In der Regele ist es auch einfacher ein lauffähiges Programm zu 
optimieren als ein optimiertes Programm zum Laufen zu bringen.

mfg

von Klaus W. (mfgkw)


Lesenswert?

DerDan schrieb:
> Damit ist klar dass ohne Overhead keine Modularität zu erreichen ist.
> Man bekommt halt nichts geschenkt.

Einige Sachen schon, z.B. namespaces.

Du hast insofern recht, als das Mehr in C++ sinnvoll sein kann.
Muß halt jeder selbst entscheiden, was man sich womit erkauft.

Ich wollte nur dafür Werbung machen, zumindest die kostenlosen Vorteile 
von C++ zu nutzen, anstatt sich sowas wie namespaces mit 
Namenskonventionen zu stricken - nur um nicht C++ anzufassen.

von Jan K. (jan_k)


Lesenswert?

Ich möchte nochmal zurückkommen auf die Erzeugung des Objektes (in C!), 
ich beziehe mich da z.B. auf

Stefan U. schrieb:
> Unabhängig davon solltest du vorsehen, mehrere Objekte vom gleichen Typ
> haben zu können. Es wäre blöd, wenn es nur einen Fifo geben kann.
>
> Also als Konstruktor-Ersatz eine Methode, wie fifo_create(), welche
> einen Struktur auf dem heap (mit malloc) erzeugt und dann einen Pointer
> darauf zurück liefert.
>
> Oder eine Funktion fifo_init() welcher als Argument ein Zeiger auf das
> Objekt übergeben wird, die initialisiert werden soll.
>
> Mit Objekt meine ich hier: Eine Variable vom Typ der Struktur.

und die anderen tollen Antworten.

Ich habe mittlerweile einen Großteil meiner Module so aufgebaut, dass 
dort wo das Modul benötigt wird ein Zeiger auf die Modul struct erstellt 
wird und dann der init Funktion übergeben wird, z.B. 
fifo_init(fifo_instance_t *this).

Schöner wäre es in der Tat, wenn fifo_init eher ein fifo_createInstance 
o.ä. wäre und den Instanz Zeiger zurückgeben würde. Gibt es Wege dies 
ohne malloc und Konsorten zu bewerkstelligen?

Hintergrund ist natürlich der, dass ich im embedded Bereich arbeite und 
der Heap beschränkt und "man" ja eigentlich Probleme mit der 
Fragmentierung umgehen möchte.
In diesen "init-" Fällen passiert die Speicherzuweisung jedoch nur 
einmalig, damit fällt das Problem der Fragmentierung weg. Ist die 
bessere Übersichtlichkeit das Verwenden von malloc wert?

Schöne Grüße!

von Ralf G. (ralg)


Lesenswert?

Jan K. schrieb:
> In diesen "init-" Fällen passiert die Speicherzuweisung jedoch nur
> einmalig, damit fällt das Problem der Fragmentierung weg. Ist die
> bessere Übersichtlichkeit das Verwenden von malloc wert?

Da kann man auch ein eigenes 'malloc' basteln:
1
void* get_mem(size_t size)
2
{
3
  static uint8_t memory[MEMORY];
4
  static uint16_t p = 0;
5
  uint16_t i;
6
7
  i = p;
8
  p += size;
9
#ifdef DEBUG
10
// Indexüberwachung für Simulatorlauf
11
  if (p > MEMORY)
12
  {
13
// irgendeine geeignete Unterbrechung/ Funktion, gerne auch mit detaillierter Fehlermeldung
14
    while(1)
15
      asm volatile ("nop");
16
  };
17
#endif
18
  return &memory[i];
19
};

von Daniel A. (daniel-a)


Lesenswert?

Ich handhabe das ja für gewönlich so, dass ich malloc vermeide, und zur 
Initialisierung entweder das Struct mit 0 initialisiere, oder mit einem 
Macro oder mit einer init funktion. Das hat den Vorteil, dass man weiss, 
wieviel Speicherplatz das ganze verbraucht. Bei einer init funktion hat 
man ausserdem den Vorteil, dass man eine das Objekt auch temporär in 
einem Funktionsscope definieren kann, und es dann auf dem stack liegt. 
Ich habe eine ähnliche Datenstruktur in meinem noch in der Entwicklung 
befindlichen Webserver Projekt:
https://github.com/Daniel-Abrecht/DPA-UCS/blob/master/src/test/ringbuffer.c
https://github.com/Daniel-Abrecht/DPA-UCS/blob/master/src/headers/DPA/utils/ringbuffer.h
https://github.com/Daniel-Abrecht/DPA-UCS/blob/master/src/utils/ringbuffer.c

Das ganze Projekt hat kein einziges malloc oder calloc, und z.B. das 
ICMP modul wird nirgens referenziert, cool, oder?

von Jan K. (jan_k)


Lesenswert?

Okay, also eine kleine statische Speicherverwaltung könnte man 
schreiben.

Daniels Projekt find' ich auch interessant, muss aber erstmal durch die 
Macros durchsteigen.

Aber wenn ich das richtig sehe, erzeugst du ja trotzdem mit 
DPA_DEFINE_RINGBUFFER( int, DPAUCS_test_ringbuffer_t, rb, 4, false ); 
eine statische "Objektinstanz" in dem caller workspace.
Wäre es nicht auch möglich, eine Instanz in der Initfunktion lokal auf 
dem callee Stack zu erzeugen und dann die struct by value an den caller 
zurück zu geben, oder ist der overhead im Vergleich zur direkten 
Deklaration zu groß?

Auch interessant ist, ob es Sinn macht, vorher einen Zeiger auf die 
Objektinstanz (im caller) zu erzeugen und diesen zu übergeben, oder ob 
der Adressoperator keine Nachteile bringt.

von Daniel A. (daniel-a)


Lesenswert?

Jan K. schrieb:
> Aber wenn ich das richtig sehe, erzeugst du ja trotzdem mit
> DPA_DEFINE_RINGBUFFER( int, DPAUCS_test_ringbuffer_t, rb, 4, false );
> eine statische "Objektinstanz" in dem caller workspace.
> Wäre es nicht auch möglich, eine Instanz in der Initfunktion lokal auf
> dem callee Stack zu erzeugen und dann die struct by value an den caller
> zurück zu geben, oder ist der overhead im Vergleich zur direkten
> Deklaration zu groß?

Dazu müsste ich das Makro und das Struct etwas anpassen, im moment hat 
es im Struct z.B. noch einen const member, und beim Makro müsste ich das 
static wegnehmen, usw. Das Hauptproblem ist aber nicht das Strukt by 
value zurückzugeben, sondern dass das Makro vorher noch einen Buffer 
definiert. Wenn ich das Macro in eine init funktion umschreiben wollte, 
müsste ich den Buffer der init funktion übergeben, wenn ich malloc 
vermeiden will, weil ein Buffer innerhalb der init Funktion nach 
verlassen der init Funktion nichtmehr auf dem Stack wäre, was wiederum 
auch nicht besonders sinvoll wäre, was wiederum der grund war warum ich 
ein Makro genommen hatte.

von fürn hugo (Gast)


Lesenswert?

Wenn du kannst dann nimm C++.
Mit C musst du entweder die Struktur in den Header geben damit Variablen 
dieser Struktur angelegt werden können, und damit sind alle Member der 
Struktur "puplic" oder du musst den Speicher allozieren.
Warum nicht gleich C++?

von Jan K. (jan_k)


Lesenswert?

Ich kann/darf kein C++ verwenden. Das wird zwar evaluiert, ist aber in 
naher Zukunft nicht vorgesehen. Daher C.

Die Buffergeschichte macht Sinn soweit.

Danke erstmal für euer Feedback, überlege mal weiter :)

von Christopher J. (christopher_j23)


Lesenswert?

Jan K. schrieb:
> Ich kann/darf kein C++ verwenden. Das wird zwar evaluiert, ist aber in
> naher Zukunft nicht vorgesehen. Daher C.

Mach dir nichts draus. Das geht auch alles in C.

Schau dir am besten mal an wie das heutzutage in kleinen 
Betriebssystemen gehandhabt wird (im Prinzip so wie von W.S. weiter oben 
beschrieben). Als Beispiel hier mal die Implementierung im LK (Little 
Kernel):

In der uart.h steht nur
1
void uart_init(void);
2
void uart_init_early(void);
3
4
int uart_putc(int port, char c);
5
int uart_getc(int port, bool wait);
6
void uart_flush_tx(int port);
7
void uart_flush_rx(int port);
8
void uart_init_port(int port, uint baud);
9
10
/* panic-time uart accessors, intended to be run with interrupts disabled */
11
int uart_pputc(int port, char c);
12
int uart_pgetc(int port);

Diese Funktionen müssen dann für die jeweilige Hardware implementiert 
werden und man kann mit dem gleichen Header auf verschiedensten Systemen 
und Architekturen arbeiten, von Cortex-M0 über MIPS bis x86.
FIFO bzw. Ringbuffer tauchen hier gar nicht mehr direkt auf.

Nun kann man auf verschiedene Weise diese Funktionen implementieren. Als 
Beispiel mal vom selben Projekt eine für den STM32F4 (mit STD-Lib):

uart.c (F4)
https://github.com/littlekernel/lk/blob/master/platform/stm32f4xx/uart.c

und eine für den STM32F7 (mit HAL):

uart.c (F7)
https://github.com/littlekernel/lk/blob/master/platform/stm32f7xx/uart.c


Beide Varianten nutzen die gleiche Implentierung eines circular buffers:

cbuf.c
https://github.com/littlekernel/lk/blob/master/lib/cbuf/cbuf.c

cbuf.h
https://github.com/littlekernel/lk/blob/master/lib/cbuf/include/lib/cbuf.h


In beiden Fällen (F4 und F7) wird der Speicher dynamisch reserviert, was 
ich persönlich für Mikrocontroller (so weit es geht) versuche zu 
vermeiden, weil ich gerne wissen möchte, wieviel ich benötige, bzw. wie 
viel Luft noch da ist. Im Fall des LK wäre das jedoch mit ein paar 
Änderungen verbunden. Man müsste in der cbuf.c eine Funktion 
implementieren in der man statisch allozierten Speicher von außen (per 
Pointer) durchreichen kann und diesen Speicher dann innerhalb der uart.c 
reservieren. Das könnte man dort dann auch innerhalb der #ifdefs machen 
um direkt für jeden einzelnen UART Speicher zu reservieren (oder eben 
nicht, falls nicht angewählt).

Nur damit hier nicht der falsche Eindruck entsteht:
Um portabel zu programmieren brauchst du kein OS. Das sollte lediglich 
ein Beispiel für eine Kapselung in C sein, bei der die Modularität und 
die Portierbarkeit erhöht werden.

von Jan K. (jan_k)


Lesenswert?

Nochmal danke für die Antworten.

Weil das Argument mit der UART jetzt schon häufiger kam:
Ich muss ja mein Fifo Modul nicht im Hauptprogramm benutzen, sondern 
kann ja wohl sehr gut innerhalb des UART Moduls benutzen. Ich bau doch 
nicht jedes mal was Pufferähnliches, wenn ich irgendwann mal ein Modul 
dafür geschrieben habe?!
Oder anders rum - wenn etwas von der Uart verfügbar ist muss ich das 
doch trotzdem weiter verarbeiten und dann entweder wieder in nen fifo 
oder direkt in ne Statemaschine o.ä. packen. Eure uart.h ist doch nicht 
das oberste Modul?! Klar sieht die Schnittstelle da schön aus, aber es 
geht doch eben noch weiter...

Oder bei W.S. USB Interface - was mache ich, wenn ich mehrere Instanzen 
haben will, wenn es 2 Ports gibt? Dann muss ich eben DOCH wieder einen 
Objektzeiger übergeben. Und genau darum geht es hier - WO erstelle ich 
meine Instanzstruktur, die dann eben entweder zurückgegeben wird oder 
vom caller bereits erstellt wird, publiziere ich die gesamte Struktur im 
Header, oder teils teils oder nur die Forwarddeclaration etc pp

Btw, in Christophers cbuf.h wird genau das gemacht was ich ja eben auch 
versuche - da wird einfach die gesamte Struktur exportiert.

Denke oben wurden alle möglichen Methoden vorgestellt, ich probiere 
gerade ein paar aus.

von Thomas Z. (tezet)


Lesenswert?

Du kannst auch in fifo.c ein statisches Array deiner Struct anlegen, 
dann brauchst du diese struct auch nicht in fifo.h zu veröffentlichen.
Eine GibsMir-Funktion liefert nur einen Index (Byte) auf den nächsten 
freien Slot.
Nachteil: Es kann nur eine vorher festgelegte Anzahl von fifos geben. 
Aber das kannst du auch in einer fifoConfig.h einstellen.

Fazit: jedes Konzept hat Vor- und Nachteile - also einfach die kleinere 
Kröte schlucken

von Achim (Gast)


Lesenswert?

Wenn nur der Pointer (und nicht die Struktur) veröffentlicht wird, hat 
man eine weit bessere Kapselung als in C++ möglich.

Das hier von Jan K und Sebastian beschriebene Problem
sebastian schrieb:
> Das Problem, dass du die Struktur innerhalb des FIFO Moduls allokieren
> musst, hast du immer - egal, ob du alles oder nur einen Teil der member
> hinter einem Zeiger auf eine Struktur mit Forward Deklaration
> versteckst. Weils der benutzer halt leider nicht mehr kann. Er weiß ja
> nicht, wie groß die Struktur ist.

ist keines. In die Header-datei stellt man entweder einen Dummy-Typen 
oder ein entsprechendes #define für den Speicherplatz.
1
struct fifo_t;
2
typedef struct fifo_t * pFifo_t;
3
#define FIFO_BYTES(elemnts, elementsize) (elements*elementSize+54)
4
pFifo_t InitFifo(uint8 *buffer, size_t elements, size_t elementSize)
5
6
// ... und im code dann 
7
8
static pFifo_t myLocalFifo1;
9
10
void InitThisModul(void)
11
{
12
static uint8_t myLocalBuffer1[FIFO_BYTES(43,2)];
13
14
    myLocalFifo = InitFifo(myLocalBuffer1, 43,2);   
15
}

Die +54 in FIFO_BYTES entsprechen dem fiktiven Platzbedarf von fifo_t. 
Dem Modul Fifo.c ist es nämlich egal, wo die liegt. Damit kann ich 
beliebig viele Fifos nutzen, ohne auch nur einen Platz unnötig zu 
reservieren.

Die Struktur von fifo_t steht nur in fifo.c (keine private_fifo.h). Die 
54 Bytes werden mit einem Compile-Time-Assert geprüft.

Die Verwendung von 43 und 2 an zwei stellen ist natürlich unschön 
(Konstanten machen es nicht besser). Hier kann man je nach Geschmack die 
beiden aufeinanderfolgenden Zeilen in ein Makro packen oder andere 
Tricks nutzen.

von Bernd K. (prof7bit)


Lesenswert?

Jan K. schrieb:
> publiziere ich die gesamte Struktur im Header

Ja. Das ist das kleinere Übel und spart einen Haufen Verrenkungen, auch 
das Debuggen wird einfacher. Sichtbarkeit (bzw deren Einschränkung) wird 
maßlos überbewertet und kann genauso gut per Konvention und Disziplin 
sichergestellt werden, um Größenordnungen wichtiger ist Modularität und 
Entkopplung, denn nur dort kommt reale Arbeitsersparnis und 
Übersichtlichkeit her.

von chris_ (Gast)


Lesenswert?

Autor: Jan K. (jan_k) schrieb
>Das funktioniert soweit auch. Ist diese Herangehensweise in Ordnung? Zum
>Beispiel muss ich ja hier den Puffer im caller erstellen. Sollte ich die
>fifos und die Puffer static deklarieren, damit sie nur in
>modul_braucht_fifo.c sichtbar sind?

Wenn man in C objektorientiert programmieren will, muss man sich fragen, 
was in C eine Klasse sein könnte.

Eine Struktur "struct" entspricht hier am ehesten der Klasse in C++. Nur 
beinhaltet die Struktur keine Memberfunktionen, sondern nur die 
Membervariablen.
Wenn Du die Struktur extern erzeugst, ist das wie in C++ eine Klasse 
instanzieren, also ein Objekt erzeugen. Wenn Du das Static tust, hast Du 
quasi nur ein Objekt und kannst nicht viele Objekte der Klasse erzeugen.
Deshalb ist es sinnvoll, einen Zeiger auf die Struktur zu übergeben, 
damit man viele Objekte der gleichen Klasse erzeugen kann ( wenn man sie 
den braucht ).
1
fifoErrorCodes_t fifo_init( fifo_t *f, void *buffer, size_t bufferSize, size_t elementSize );

Die Frage ist, warum Du buffer, buffersize und elementSize nicht auch in 
die Struktur packst.

von S. R. (svenska)


Lesenswert?

chris_ schrieb:
> Die Frage ist, warum Du buffer, buffersize und elementSize nicht auch in
> die Struktur packst.

Wenn bufferSize und elementSize Compilezeitkonstanten sind, kann der 
Compiler wesentlich besser optimieren, z.B. bei Zweierpotenzen Modulo 
mit AND ersetzen.

von DerDan (Gast)


Lesenswert?

Bernd K. schrieb:
> Sichtbarkeit (bzw deren Einschränkung) wird
> maßlos überbewertet und kann genauso gut per Konvention und Disziplin
> sichergestellt werden, um Größenordnungen wichtiger ist Modularität und
> Entkopplung, denn nur dort kommt reale Arbeitsersparnis und
> Übersichtlichkeit her

Sehr sehr wahre Worte.
Kann ich nur Unterstreichen.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Eingeschränkte Sichtbarkeit kann aber Entkopplung und Modularität
fördern, auch wenn sie keine Voraussetzung dafür ist.

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.