Forum: Mikrocontroller und Digitale Elektronik [C auf µCs] Mehrere Callbacks ohne dyn. memory


von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Hallo,
callbacks werden ja gerne verwendet, um Abhängigkeiten von SW-Teilen zu 
reduzieren. Jetzt ärgere ich mich gerade mit einer C-Library herum, die 
es zwar erlaubt, einen callback zu installieren, aber auch genau nur 
einen.

Jetzt frage ich mich gerade ob es ggf. ein übliches Pattern sein könnte, 
beim Installieren nicht nur den Funktionszeiger mit zugeben, sondern 
auch den Speicher, der benötigt wird, um eine verkettete Liste mit 
callbacks auf zu bauen?

Beispiel:
1
// library.h
2
typedef void (*callback_t)( int update );
3
4
struct callback_node
5
{
6
    callback_t              callback;
7
    struct callback_node*   next;
8
};
9
10
void install_callback( struct callback_node* );
11
12
void update( int value );
13
14
// library.h
15
static struct callback_node* nodes = 0;
16
17
void install_callback( struct callback_node* node )
18
{
19
    node->next = nodes;
20
    nodes = node;
21
}
22
23
void update( int value )
24
{
25
    for ( struct callback_node* n = nodes; n; n = n->next )
26
        n->callback( value );
27
}
28
29
// main.c
30
void showupdates( int update )
31
{
32
33
}
34
35
struct callback_node showupdates_node = { showupdates };
36
37
int main()
38
{
39
    install_callback( &showupdates_node );
40
}

das würde erlauben, mehrere Callbacks zu installieren, ohne dynamischen 
Speicher zu verwenden oder mit fest dimensionierten Array zu arbeiten. 
Der overhead ist mit einem pointer auch recht überschaubar. Zusätzlich 
könnte man in callback_node auch noch einen void-pointer als User 
argument unterbringen.

Ist das ein übliches Pattern in C, das evtl. sogar einen Namen hat?

mfg Torsten

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Was, nicht mal Kommentare zur Namensgebung? ;-)

von Mark B. (markbrandis)


Lesenswert?

Torsten R. schrieb:
> Ist das ein übliches Pattern in C, das evtl. sogar einen Namen hat?

Hier sind verschiedene Entwurfsmuster erwähnt, die man in C mittels 
Callback-Funktionen implementieren kann:

http://stackoverflow.com/questions/946834/is-there-a-design-pattern-that-deals-with-callback-mechanism

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Torsten R. schrieb:
> Ist das ein übliches Pattern in C

Ist doch egal, ob sowas „üblich“ ist oder nicht: wenn du es sinnvoll
brauchen kannst, dann kann das eine Lösung sein.  Viele werden das
halt eher nicht brauchen.

Man muss die Scheu vor malloc() übrigens auch nicht übertreiben. ;-)

von Tom (Gast)


Lesenswert?

Torsten R. schrieb:
> sondern
> auch den Speicher, der benötigt wird, um eine verkettete Liste mit
> callbacks auf zu bauen?

Man verschleppt damit die ganzen schmutzigen Implementierungsdetails der 
verlinkten Liste in main. Außerhalb von library.c muss eigentlich keiner 
sehen können, ob die callbacks in einem festen Array oder einer linked 
list oder was auch immer wohnen.
Mir wäre das zu hässlich und ich würde die library.h auf
1
void install_callback( callback_t fun);
2
void update(int value); // die haette man auch run_all_callbacks() o.ae. nennen koennen.
eindampfen. Echte Embedded-Helden mit Abstraktionsallergie werden sich 
aber wahrscheinlich unwohl fühlen, wenn sie nicht genau wissen, was 
install_callback() genau tut.


Torsten R. schrieb:
> Was, nicht mal Kommentare zur Namensgebung? ;-)
Du hättest noch ein paar verschiedene Dinge mehr "update" nennen können 
;)

von Peter D. (peda)


Lesenswert?

Torsten R. schrieb:
> struct callback_node showupdates_node = { showupdates };

Torsten R. schrieb:
> ohne dynamischen
> Speicher zu verwenden oder mit fest dimensionierten Array zu arbeiten.

Was soll denn der Vorteil von einzelnen über das ganze Programm 
verstreuten Callback Variablen sein, gegenüber einem Array?

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Hallo Peter,

Peter D. schrieb:
> Was soll denn der Vorteil von einzelnen über das ganze Programm
> verstreuten Callback Variablen sein, gegenüber einem Array?

der Vorteil wäre Lokalität. Ein Array bräuchte eine Dimension, die 
entweder global festgelegt werden müsste, oder dynamisch zur Laufzeit 
wachsen müsste.

Das Muster hätte den Vorteil, dass kein dynamischer Speicher verwendet 
werden muss und auch keine feste Array-Größe vorgegeben werden muss (und 
damit keine Laufzeitfehler erzeugen kann).

Die Nachteile wären: leicht höhere Komplexität bei der Nutzung, die 
zusätzliche Fehlermöglichkeit, den Node auf dem Stack zu alloziieren und 
der Overhead einer verketten Liste (gegenüber einem Array).

mfg Torsten

von Peter D. (peda)


Lesenswert?

Ich hab hier mal einen Scheduler mit Array vorgestelt:

Beitrag "Wartezeiten effektiv (Scheduler)"

Der Vorteil ist, daß das Array nur so groß sein muß, wie die maximale 
Anzahl gleichzeitig aktiver Callbacks.

von 7856ujtzuitzu (Gast)


Lesenswert?

bin auch freund der linked list ...
ich verwende das ganz schmerzfrei auf einem Cortex M

ich hatte nurprobleme bie verwendung von calloc statt malloc
irgendiwe stieg der µC manchmal genau beim calloc aus.

mit malloc + memset klappt das aber ohne probleme

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Hallo Peter,

Peter D. schrieb:

> Der Vorteil ist, daß das Array nur so groß sein muß, wie die maximale
> Anzahl gleichzeitig aktiver Callbacks.

Der Nachteil ist aber dann, die Ermittlung der maximalen Anzahl 
gleichzeitiger Callbacks. In Deinem Beispiel könnte man nach "timeradd" 
grepen und die Anzahl der Fundstellen als Anhaltspunkt nehmen um 
MAXTIMERS zu dimensionieren. Oder man könnte warten, bis Fehler zur 
Laufzeit auftreten und dann MAXTIMERS erhöhen, bis keine Fehler mehr 
auftreten. Gerade bei Timer kann ich mir vorstellen, dass es nicht ganz 
einfach sein wird, die maximale Anzahl gleichzeitig aufgesetzter Timer 
zu ermitteln (und mit dem Entwicklungsfortschritt laufend anzupassen).

Wenn Du die Library in mehreren Projekten einsetzt, musst Du sie dann 
zumindest für jedes Projekt neu Dimensionieren und auch neu übersetzen.

Aber gerade in Deinem Fall, wäre es eigentlich besonders hilfreich, wenn 
Du das Alloziieren der Knoten, dem Aufrufer überlassen würdest, da Du 
für die Reihenfolge der Timer eh eine verkette Liste pflegst.

Um so eine Implementierung, könnte man dann dünne Wrapper schreiben, die 
den Speicher dann z.B. aus einem heap oder einem array alloziieren.

mfg Torsten

Btw: wenn Du neben der Liste t_first noch eine zweite Liste t_empty 
führen würdest, könntest Du Knoten in O(1) alloziieren und bräuchtest 
T_FREE nicht. timerinit() würde dann alle Elemente in t_empty eintragen.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Tom schrieb:
> Torsten R. schrieb:
>> Was, nicht mal Kommentare zur Namensgebung? ;-)
> Du hättest noch ein paar verschiedene Dinge mehr "update" nennen können
> ;)

Ja, oder vielleicht in der Variante "smart_update" ;-)

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.