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:
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
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. ;-)
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
voidinstall_callback(callback_tfun);
2
voidupdate(intvalue);// 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
;)
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?
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
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.
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
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.
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" ;-)