Nop schrieb:> X4U schrieb:>>> Bei C von zufrieden zu reden ist schon schwierig. Es ist halt ein>> Kompromiss. Man ist dicht genug auf der Hardware, hat aber eine Art>> Hochsprache besser evtl. mit "universeller Makroassembler" definiert.>> Exakt. Die Dosis macht das Gift.>> Und außerdem ist einer der Vorteile von C, daß es einen Haufen> Abstraktionen nicht bietet und man, wie Torvalds schon anmerkte, damit> auch Leute draußen hält, die sich mehr daran hochziehen als an der> eigentlichen Aufgabe. Ich finde, dieser Thread bestätigt Torvalds' Rant> recht beeindruckend.
Was willst du eigentlich ständig mit Torvalds? Gibt es überhaupt
irgendwas auf der Welt worüber Linus keinen Rant starten kann? Das er
Linux initiiert hat und ein führender Entwickler ist, schließt nicht
aus, dass er Schwachsinn über C++ schreiben kann.
Nop schrieb:> PeterPan schrieb:>> auf nem ARM Cortex muss man>> sich sowas nicht antun, da ist der ROM im gleichen Adressraum.>> Es kann aber verschiedene Sorten disjunktes RAM geben. SRAM, CCM,> Backup-RAM. Da muß man schon entscheiden, welche Variablen wohin gehen> sollen.
Das regle ich über mein Linkerscript und den dazugehörigen Sections.
Diese ganze Dogmatik erinnert mich an einen Labview Anhänger, der mir
weiß machen wollte, dass Grafische Programmierung generell den
Textsprachen überlegen ist. Dass C Programmieren das schlimmste auf der
sei, dass man C ja sowas von gar nicht debuggen kann und allerhand
anderen Schwachsinn. Wobei er nie mit C programmiert hat oder mit
irgendeiner anderen textbasierte Programmiersprache...
Nachdem wir nun endlich geklärt haben, dass C in allen Belangen besser
ist würde mich interessieren, wie man hier im Forum eigentlich zu Rust
und Konsorten steht?
Rust bietet ja quasi die Möglichkeit zur Abstraktion ohne eigentliche
Klassen? Ich würde jemanden bitten mir hier meine Meinung zu justieren,
ich weiß nämlich nicht ob ich Rust jetzt schlecht finden muss?
Vincent H. schrieb:> Nachdem wir nun endlich geklärt haben, dass C in allen Belangen besser> ist würde mich interessieren, wie man hier im Forum eigentlich zu Rust> und Konsorten steht?>> Rust bietet ja quasi die Möglichkeit zur Abstraktion ohne eigentliche> Klassen? Ich würde jemanden bitten mir hier meine Meinung zu justieren,> ich weiß nämlich nicht ob ich Rust jetzt schlecht finden muss?
Du mußt. Es ist !C.
Jörg W. schrieb:> Wilhelm M. schrieb:>> Nur __flash, __flash1, ... als spezieller qualifier für>> named-address-spaces eben nicht, weil das eben non-standard>> Erweiterungen sind>> … die man dort offenbar aber nicht haben will. Ansonsten hätte ja> nichts dagegen gesprochen, dass Johann sie auch für C++ aktiviert.
Da ist nix mit "aktivieren". Qualifier sind eine Erweiterung des
C-Fronts und des GCC Middle-Ends, und das AVR-Backend implementiert
einige extra Qualifier.
Aber das C++-Front anzupassen (geschweige denn die Spez dahingehend zu
erweitern), ist noch mal ne gan zandere Geschichte. Wenn man sich alle
Abartigkeiten von C++ zusammen vorstellt, bekommt man ne Idee, das sowas
in einem Compiler FrontEnd nicht so ganz trivial umzusetzen (und zu
testen) ist.
Was die Spez angeht:
> "WG21 has been burnt by volatile"
hieß es mal in einem ML-Thread zu Thema: WG21 ist, was Qualifier
angeht, ein gebranntes Kind und wird den Teufel tun, Qualifier nochmals
anzufassen. Zumal ISO/IEC DTR 18037 auch "Embedded-C" heißt, und das
ist deutlich unter dem Radar des momentanen Entwicklungsfokus, wo es
z.B. darum geht, wie man ISAs mit SIMD mit 4x double effizient
parallelisiert...
Dabei geht DTR 18037 weit über das vom GCC angebotene
Qualifier-Interface hinaus, indem es sogar User-defined Qualifier
einführt (nicht von GCC implementier), die ermöglichen, neue Qualifier
auf Applikationsebene einzuführen. Aber wie gesagt: Das Radar...
Außerdem greift das non-standard Argument auch nicht wirklich. Oft
waren Spracherweiterungen wie GNU-C oder GNU-C++ Vorreiter von
Erweiterungen des Standards, die Praxistauglichkeit demonstrierten.
Erweiterungen am grünen Tisch zu entwerfen ist nicht immer so glücklich,
wie die Diskussion um atomic zeigt, und ein großer Teil von
Embedded-C kann man getrost in die Tonne treten da unbrauchbar (obwohl
sie von einem WG14 Mitglied stammen!).
Inzwischen gibt es aber eher ein Henne-Ei Problem: Die Compilerbauer
wollen nicht mehr gerne nichtstandard Erweiterungen einbauen, weil die
oft problematisch sind (Kompatibilität, Wartung, Stabilität), und was in
den Standard zu bringen "Out of thin Air" ist noch aussichtsloser.
Ich verstehe aber immer noch nicht, warum __flash in Embedded-C ein
Argument gegen C und für C++ sein soll.
Vincent H. schrieb:> Nachdem wir nun endlich geklärt haben, dass C in allen Belangen besser> ist würde mich interessieren, wie man hier im Forum eigentlich zu Rust> und Konsorten steht?
Um Gottes Willen, wenn hier viele schon mit dem alten, etablierten und
bewährten C++ überfordert sind, was glaubst du ist erst bei einer
neuartigen und innovativen (Ownershipsystem z.B.) Sprache los ist, die
dazu auch noch die gleiche Mächtigkeit anstrebt. Vor allem wenn sich
hier schon so wenige mit C++ auskennen, dann wird sich mit Rust wohl
kaum schon einer beschäftigt haben (aber eine Meinung haben sie ganz
sicher schon ;) ).
Vincent H. schrieb:> Rust bietet ja quasi die Möglichkeit zur Abstraktion ohne eigentliche> Klassen? Ich würde jemanden bitten mir hier meine Meinung zu justieren,> ich weiß nämlich nicht ob ich Rust jetzt schlecht finden muss?
Die structs sind doch quasi Klassen. Das OOP funktioniert dort ein
bisschen anders (man könnte auch sagen, dass es
reduzierter/vereinfachter ist), so ähnlich wie bei Go. Vererbung gibts
an sich nicht, aber Traits (also Interfaces wie in C#/Java) und
natürlich Komposition, womit sich sowas wie Vererbung nachbilden lässt.
Mein erster Eindruck ist sehr gut, aber es ist keine Sprache für
Anfänger sondern eher was für Leute, die viele Dinge an C++ zu schätzen
wissen, aber mit ein paar Dingen nicht zu frieden sind.
Jörg W. schrieb:> Namend memory spaces sind Bestandteil eines Standardvorschlags für> Embedded C. Johann wollte darauf hinaus, dass dieser Vorschlag jedoch> auf einem standardmäßig bei C vorhandenen Feature basiert, den type> qualifiers, die es bei C++ nicht gibt.
Wenn ich mich recht entsinne, kennt C die Typqualifizierer "const" und
"volatile" erst seit C89 und hat sie lustigerweise ausgerechnet von C++
übernommen... correct me if I'm wrong. Zudem kommt IIRC __flash vom IAR,
wurde von Atmel für den GCC übernommen, und ist bisher nur ein
Vorschlag, aber eben kein Standard.
PeterPan schrieb:> Nop schrieb:>> [...]> Was willst du eigentlich ständig mit Torvalds?
Der Linus ist eben dem Nop sein heiliger Gott, der immer Recht hat und
sich noch niemals nie nicht geirrt hat. Und weil unserem Nop hier leider
gerade die eigenen Argumente ausgegangen sind (so er denn überhaupt
jemals welche gehabt haben sollte) und der Linux mal was gesagt hat, das
dem Nop gefällt und ihm gerade in den "argumentativen" Kram paßt, darum
reitet der Nop hier immer wieder auf dem Linus und seinen Aussagen
herum, daß der arme Linus schon ganz wund dabei wird.
> Gibt es überhaupt irgendwas auf der Welt worüber Linus keinen Rant> starten kann?
Ja, Tove. ;-)
@nop:
Mit dem was Du da von Dir gibst sagst Du, dass meine Kollegen und ich
unsere Arbeit nicht richtig machen, weil wir diese Unsprache C++
hernehmen und uns damit sicher verkünsteln. Ich glaube Du kannst, wie es
Dir hier viele schon nahe gelegt haben, C++ schlicht weg nicht
ordentlich beurteilen, weil Du es einfach nicht richtig kennst. Somit
ist dein Vermögen darüber qualifizierte Kommentare abzugeben nicht
sonderlich groß. Sorry für die Direktheit, aber es ist nun mal so und Du
willst es einfach nicht wahr haben.
@all:
Ihr hättest das hier mal lesen sollen, bevor ichs selbst zensiert hab :D
Wär ja schade drum wenn die Kernaussage des Posts nicht stehen bleibt.
Grüße Oliver
Sheeva P. schrieb:> Zudem kommt IIRC __flash vom IAR,
Nicht nur, auch viele andere Compiler handhaben das so.
Daher ja auch der Standardvorschlag. Solche Dinge werden ja nicht von
den Normungsgremien erfunden, sondern sie bemühen sich in erster Linie,
gängige Praxis zu standardisieren.
> wurde von Atmel für den GCC> übernommen,
Atmel? Die haben damit nichts zu tun. Das ist Johanns Werk.
> und ist bisher nur ein Vorschlag, aber eben kein Standard.
Besser als nichts. Insbesondere eben besser als das progmem-Attribut.
Warum man named memory spaces im Embedded-Bereich gebrauchen kann,
wurde ja schon genannt. Selbst wenn sie alle über einen flat address
space zugegriffen werden können, kann es halt notwendig sein, dass
der Programmierer verschiedene Speichersegmente selbst beim RAM explizit
auswählen möchte. Ohne named memory spaces geht das nur über völlig
proprietäre Lösungen, Pragmas, Attribute etc. pp.
Sheeva P. schrieb:>> Also mal ganz vulgär:>> nicht "SetzePin(Port,Nummer,Zustand);">> sondern "SchalteMotorEin();">> ...oder eben: "motor.einschalten();". Das läßt sich mit einfacher> Vererbung ganz einfach, elegant und kostenlos umsetzen. Das bedarf nur> einer simplen Elternklasse, die einen Pin abstrahiert -- und ist dann> mit einer anderen Elternklasse, die dasselbe Interface auf einer anderen> Controllerfamilie implementiert, sogar portabel.
Nein, du fährst damit in die falsche Richtung.
Für den Motor kann man kein Objekt gebrauchen, denn er läßt sich nicht
ableiten (wovon denn auch!) - und Vererbung hat auch keinen Sinn, denn
der Motor (oder der Schütz oder die Lampe) hat eben auch nichts zu
vererben (an wen denn auch!).
Objektorientierte Programmierung ist eine Domäne von äußerlich gleichen,
aber innerlich verschiedenen Dingen, also von einem Vorfahren
abgeleitete Objekte wie z.B. grafische Elemente auf dem Bildschirm. Aber
das gilt ausdrücklich NICHT für Hardware. Die ist immer ein Unikat.
Abgesehen davon denkst du schon wieder an etwas in der Art von vererbtem
und abstrahierten Pin - das ist falsch, weil einschränkend. Sowas wie
"SchalteMotorEin() kann auch das Aufsetzen eines Timermoduls sein, wenn
es sich z.B. um einen Schrittmotor handelt. Es kann auch ein I2C Zugriff
sein, wenn es ein intelligenter Stellmotor ist. Natürlich kann es auch
ein simpler Schütz sein, der den Motor erstmal in Stern anlaufen läßt um
dann nach einiger Zeit auf Dreieck umgeschaltet zu werden. Du siehst,
daß sich sowas einfach NICHT oder nur mit sinnlos aufgesetztem Krampf in
eine Objakthierarchie fassen läßt.
Also, denke an "KISS" und laß als sinnvollste Schnittstelle die simple
Aktion "SchalteMotorEin()" einfach stehen. Das ist so ziemlich die
sinnvollste Abstraktion in diesem Falle.
W.S.
W.S. schrieb:> Objektorientierte Programmierung ist eine Domäne von äußerlich gleichen,> aber innerlich verschiedenen Dingen, also von einem Vorfahren> abgeleitete Objekte wie z.B. grafische Elemente auf dem Bildschirm. Aber> das gilt ausdrücklich NICHT für Hardware. Die ist immer ein Unikat.
Objektorientierung geht auch ohne Vererbung. Ein Objekt kann auch
einfach nur zusammengehörige Sachen sehr schön kapseln. Man kann dann
Abhängigkeiten bei Konstruieren mit reingeben (Composite-Pattern), was
UnitTests erheblich erleichtert. Da kommt mir persönlich das was die
Sprache C++ bietet sehr entgegen. Es setzt aber vorraus, dass man sich
darauf einlässt beim Softwareentwurf in Objekten zu denken, sonst macht
das natürlich wenig Sinn.
Grüße Oliver
W.S. schrieb:> Objektorientierte Programmierung ist eine Domäne von äußerlich gleichen,> aber innerlich verschiedenen Dingen
… also beispielsweise einem Dutzend Motoren eines Roboters? ;-)
Oliver J. schrieb:> Ihr hättest das hier mal lesen sollen, bevor ichs selbst zensiert hab :D> Wär ja schade drum wenn die Kernaussage des Posts nicht stehen bleibt.
Bitte, reg' Dich nicht auf. Ich weiß, angesichts der Ignoranz und der
fast schon gebetsmühlenartig immer wieder vorgebrachten Vorurteile, die
darüber hinaus aus Unkenntnis noch nicht einmal die eigenen sind, ist
das manchmal leichter gesagt als getan.
Aber nimm' es mit Humor: Du darfst und kannst die Vorzüge von C++
genießen, und er eben nicht. Selbstgewähltes Schicksal, nennt man das
wohl. ;-)
Ich kann nur sagen keine Angst vor C++, jeder der C++ kann, kann auch C
und teilweise auch umgekehrt.
Ein guter Programmierer hat keine Angst vor verschiedenen
Programmiersprachen, er kann sogar viele davon anwenden. Fürs Hobby
lohnt sich der Aufwand freilich nicht viele Sprachen zu lernen.
Wer kleine überschaubare Programme schreibt der nehme C, wer mehr will,
nehme C++ aber verwende nicht die allzu verrückten Sachen, ich denke,
dass man bei Klassen einen guten Schlussstrich machen kann.
Jörg W. schrieb:> Sheeva P. schrieb:>>> wurde von Atmel für den GCC>> übernommen,>> Atmel? Die haben damit nichts zu tun. Das ist Johanns Werk.
Wenn Du das sagst, wird das so sein. Ich hatte diese Seite [1]
fälschlich so interpretiert, daß Atmel das Verdienst beansprucht.
Entschuldige, Johann: ich wollte Deine hervorragenden Leistungen und
Verdienste um den AVR-GCC nicht schmälern.
>> und ist bisher nur ein Vorschlag, aber eben kein Standard.>> Besser als nichts.
Ja, natürlich, keine Frage. Aber ein nützliches, dennoch aber (noch)
nicht einmal standardisiertes einzelnes Feature einer Sprache als
Argument gegen eine andere Sprache zu verwenden, während andererseits
besonders wichtige, standardisierte Features wie etwa die wesentlich
bessere Typsicherheit der anderen Sprache komplett ignoriert werden...
verzeih', aber das erscheint mir weder seriös noch objektiv.
[1]
http://www.atmel.com/webdoc/avrlibcreferencemanual/porting_1iar_porting_flash.html
Sheeva P. schrieb:> Aber nimm' es mit Humor: Du darfst und kannst die Vorzüge von C++> genießen, und er eben nicht. Selbstgewähltes Schicksal, nennt man das> wohl. ;-)
Da hast Recht ;)
Grüße Oliver
W.S. schrieb:> Sheeva P. schrieb:>>> Also mal ganz vulgär:>>> nicht "SetzePin(Port,Nummer,Zustand);">>> sondern "SchalteMotorEin();">>>> ...oder eben: "motor.einschalten();". Das läßt sich mit einfacher>> Vererbung ganz einfach, elegant und kostenlos umsetzen. Das bedarf nur>> einer simplen Elternklasse, die einen Pin abstrahiert -- und ist dann>> mit einer anderen Elternklasse, die dasselbe Interface auf einer anderen>> Controllerfamilie implementiert, sogar portabel.>> Nein, du fährst damit in die falsche Richtung.
Ich fürchte, daß Du hier gerade der Geisterfahrer bist.
> Für den Motor kann man kein Objekt gebrauchen, denn er läßt sich nicht> ableiten (wovon denn auch!) - und Vererbung hat auch keinen Sinn, denn> der Motor (oder der Schütz oder die Lampe) hat eben auch nichts zu> vererben (an wen denn auch!).
Zunächst einmal wird der Motor (zumindest Dein vereinfachtes Modell, das
nur die Zustände "ein" und "aus" kennt) aus Sicht des Mikrocontrollers
über einen GPO-Pin angesteuert. Für den Mikrocontroller ist Dein Motor
darum nur ein GPO-Pin, der die Zustände "an" und "aus" hat und dessen
aktueller Zustand nur über die Operationen (OO-Slang: Methoden)
"einschalten" und "ausschalten" verändert werden kann.
An dieser Stelle kommen bereits zwei wichtige Eigenschaften der OOP zum
Tragen. Datenkapselung heißt an dieser Stelle, daß der Zustand des
Motors nur über die Operationen (im OO-Slang manchmal auch Nachrichten
genannt) "einschalten" und "ausschalten" verändert werden kann. Und die
Vererbung heißt hier, daß der Motor in Wirklichkeit nur ein GPO-Pin ist,
in der OO wird ein solcher Beziehungstyp als "is-a" bezeichnet. Genau
dasselbe gilt auch für das Fahrlicht: dieselben Zustände, dieselben
Operationen, aus Sicht des Mikrocontrollers im Prinzip dasselbe Ding:
ein GPO-Pin.
Dazu existieren in meinem objektorientierten Fahrzeug auch
Bedienelemente, etwa die Zündung und der Schalter für das Fahrlicht mit
den Zuständen "ein" und "aus", letzten Endes wieder mit "is-a"
Beziehungen zu je einem GPI-Pin. Deswegen kann ich in meiner
Steuerungssoftware also etwas schreiben wie:
1
intmain(void){
2
while(true){
3
if(zuendung.ist_an()){
4
motor.einschalten();
5
}else{
6
motor.ausschalten();
7
}
8
}
9
}
Was dieser Code tut, das verstehen Du, ich, meine Ehefrau und sogar
meine zwölfjährige Zicke von Nichte auf den ersten Blick, weil der Code
so schön deskriptiv ist und die bekannten Objekte der realen Welt
nachbildet -- und Du wirst zurecht einwenden, daß das sehr ähnlich ja
auch ohne OO geht.
Aber bis hierher ist das ja nur Objektorientierung für Arme, um den Code
etwas kompakter und lesbarer zu machen. Modellieren wir die Sache doch
mal richtig: jetzt empfängt unser Motor, der immer noch nur ein GPO-Pin
mit den Zuständen "hi" und "lo" ist, eine Nachricht vom Typ Zündung, und
bietet dafür die Methode schalten(Zuendung z):
1
intmain(void){
2
while(true){
3
motor.schalten(zuendung);
4
}
5
}
Ok, die Methoden sind blöd benannt, aber darum geht es gar nicht. Es
geht vielmehr darum, daß unser Motor nur und ausschließlich mit der
Zündung ein- und ausgeschaltet werden und sich darauf verlassen kann,
daß die Zündung die passende Methode "ist_an" zur Ermittlung ihres
Zustandes bietet. Das freut Dich spätestens dann, wenn Du mit Deinem
Fahrzeug an einer Wettfahrt teilnehmen willst und dann noch einen
Notaus-Knopf anbringen und in die Steuerung einbauen mußt: dann mußt Du
nämlich nur die Klasse Zündung um Deinen Notaus-Knopf erweitern, aber
der Motor und der gesamte Rest des Fahrzeuges bleibt komplett unberührt:
"ist_an()" gibt eben nur dann true zurück, wenn a) die Zündung
eingeschaltet und b) der Notausschalter nicht gedrückt worden ist.
Nun, machen wir uns nichts vor: diese winzigen Beispiele sind zu klein
und zu schnell aus der Hand geschüttelt, um die Stärken der OO zu
zeigen. Aber sie sollen Dir ja auch nur eine kleine Idee davon geben.
> Objektorientierte Programmierung ist eine Domäne von äußerlich gleichen,> aber innerlich verschiedenen Dingen, also von einem Vorfahren> abgeleitete Objekte wie z.B. grafische Elemente auf dem Bildschirm.
Das ist zwar richtig, aber auch falsch. Im vorherigen zweiten Beispiel
"weiß" unser Motor plötzlich, wie bzw. womit er ein- und ausgeschaltet
wird, und kann den Zustand der Zündung daher selbständig abfragen. Er
ist nicht mehr darauf angewiesen, daß wir ihm explizit sagen, was er tun
soll, sondern er "weiß" es selbst.
> Aber> das gilt ausdrücklich NICHT für Hardware. Die ist immer ein Unikat.
Ich weiß nicht, was Du entwickelst, aber schon auf kleineren AVRs gibt
es häufig mehrere U(S)ARTs, die also keineswegs Unikate sind, sondern
Objekte mit (mehr oder weniger) denselben Eigenschaften. Ganz verwegene
Entwickler lassen sogar unterschiedliche Protokolle über die
verschiedenen U(S)ARTs ein- und desselben Mikrocontrollers laufen --
aber trotzdem sind es immer verschiedene U(S)ARTs.
> Abgesehen davon denkst du schon wieder an etwas in der Art von vererbtem> und abstrahierten Pin - das ist falsch, weil einschränkend. Sowas wie> "SchalteMotorEin() kann auch das Aufsetzen eines Timermoduls sein, wenn> es sich z.B. um einen Schrittmotor handelt. Es kann auch ein I2C Zugriff> sein, wenn es ein intelligenter Stellmotor ist. Natürlich kann es auch> ein simpler Schütz sein, der den Motor erstmal in Stern anlaufen läßt um> dann nach einiger Zeit auf Dreieck umgeschaltet zu werden.
Ja, natürlich kann das alles sein. Aber wo steht, daß eine Klasse Motor
zwingend von einem GPO-Pin erben muß? "motor.einschalten()" kann genau
dasselbe, was Deine Funktion auch kann: einen Timer aufsetzen, einen
I2C-Zugriff auslösen oder ein Schütz schalten. Letztlich ist die Methode
"einschalten()" ja auch nur eine Funktion und die Klasse "Motor" eine
zustandsbehaftete Datenstruktur mit Methoden zur Änderung des Zustands.
> Du siehst,> daß sich sowas einfach NICHT oder nur mit sinnlos aufgesetztem Krampf in> eine Objakthierarchie fassen läßt.
Nein, das sehe ich nicht. Was ich sehe, ist klar verständlicher Code,
der hohe Typsicherheit mit guter Les- und Erweiterbarkeit verbindet. Wo
da ein "Krampf" sein soll, obendrein ein "sinnloser", erschließt sich
mir nicht.
> Also, denke an "KISS" und laß als sinnvollste Schnittstelle die simple> Aktion "SchalteMotorEin()" einfach stehen. Das ist so ziemlich die> sinnvollste Abstraktion in diesem Falle.
Das sehe ich immer noch nicht so, tut mir leid.
Sheeva P. schrieb:> int main(void) {> while(true) {> motor.schalten(zuendung);> }> }>> Ok, die Methoden sind blöd benannt, aber darum geht es gar nicht. Es> geht vielmehr darum, daß unser Motor nur und ausschließlich mit der> Zündung ein- und ausgeschaltet werden und sich darauf verlassen kann,> daß die Zündung die passende Methode "ist_an" zur Ermittlung ihres> Zustandes bietet.
Das verstehe ich als lernwilliger aber C++ ahnungsloser wie folgt.
Für einen C++ Progger ist hier ein Objekt mit ner Eigenschaft und ner
Anweisung. Vermute ich mal. Das ganze noch gekapselt, wie auch immer das
jetzt funzen mag.
In C würde das ähnlich aussehen. Die Funktion hält die Statis local
(static), die ganze Steuerung läuft über function calls. Wer dann
unbedingt noch motor.schalten haben will macht das per struct.
Nicht ganz so hübsch zu schreiben, dafür spart man sich eine Menge
undurchsichtiger Zusammenhänge. In der Regel hat man noch den
Schaltplan, die Mechanik, Thermales, dynamic currents, EMI uswusf, zu
beachten. Da ist die schöne OOP Toolchain eher akdemisch.
Auch steigt man bei C (um doch nochmal die Abstraktionsebenen von
weiter oben zu bemühen) da wesentlich besser durch als mit Assembler.
Ein Problem lösen aber weder C noch C++.
Wenn irgendein depp (üblicherweise der Autor) die Ports über andere
Routinen toggelt und dabei statis ignoriert bzw falsch setzt schießt er
die schöne Steuerung nebst Motor. Ohne das ich was machen kann. Wie
auch? Weder µP-Hardware noch der Compiler können das verhindern.
Das geht nur in Hardware, Thema Eigensicherheit (die zu der Liste der
Aufgaben noch dazu kommt).
Fazit bisher: OOP auf'm uC ist ein Randproblem.
X4Ubbb schrieb:> Wenn irgendein depp (üblicherweise der Autor) die Ports über andere> Routinen toggelt und dabei statis ignoriert bzw falsch setzt schießt er> die schöne Steuerung nebst Motor. Ohne das ich was machen kann. Wie> auch? Weder µP-Hardware noch der Compiler können das verhindern.>> Das geht nur in Hardware, Thema Eigensicherheit (die zu der Liste der> Aufgaben noch dazu kommt).>> Fazit bisher: OOP auf'm uC ist ein Randproblem.
Genau aus dem Grund haben Klassen private und public Member. Ein Motor
Pin ist ein internes Detail des Motors und damit von außen nicht
zugreifbar.
X4Ubbb schrieb:> Wenn irgendein depp (üblicherweise der Autor) die Ports über andere> Routinen toggelt und dabei statis ignoriert bzw falsch setzt schießt er> die schöne Steuerung nebst Motor. Ohne das ich was machen kann. Wie> auch? Weder µP-Hardware noch der Compiler können das verhindern.
Der Compiler zwar nicht allein, aber mit (etwas mehr) Template-Mechanik
bekommt man auch das in den Griff.
> Das geht nur in Hardware, Thema Eigensicherheit (die zu der Liste der> Aufgaben noch dazu kommt).>> Fazit bisher: OOP auf'm uC ist ein Randproblem.
Entwickelt sich das jetzt hier zu einem Grundkurs in OOD?
X4Ubbb schrieb:>> Nicht ganz so hübsch zu schreiben, dafür spart man sich eine Menge> undurchsichtiger Zusammenhänge. In der Regel hat man noch den> Schaltplan, die Mechanik, Thermales, dynamic currents, EMI uswusf, zu> beachten. Da ist die schöne OOP Toolchain eher akdemisch.>> Auch steigt man bei C (um doch nochmal die Abstraktionsebenen von> weiter oben zu bemühen) da wesentlich besser durch als mit Assembler.
Ich weiß nicht, was dieses ganze Gerede um zu starke Abstraktion soll.
Wenn ich schreibe
1
std::iota(std::begin(c),std::end(c),42);
weiß ich, dass danach der Container c mit einer Folge von ganzen Zahlen
beginnend mit 42 gefüllt ist.
Ich muss weder wissen, WAS es für ein Container ist, etwa eine rohes
Array, ein std::array, ein std::vector, ein selbst-geschriebener ... Das
ist ein generischer Algorithmus, und der tut, was er soll. Da WILL ich
doch gar nicht hinein sehen, wie std::iota<>() realisiert ist.
X4Ubbb schrieb:> Fazit bisher: OOP auf'm uC ist ein Randproblem.
Ja, aus Sicht des E-Technikers für den Software ein nötiges Übel ist.
Und so scheinen viele Geräte programmiert zu sein. Gruseliger C Code den
nur der Autor versteht und wenn das Projekt vererbt wird muss der
nächste erstmal alles neu machen.
X4Ubbb schrieb:> In C würde das ähnlich aussehen. Die Funktion hält die Statis local> (static), die ganze Steuerung läuft über function calls. Wer dann> unbedingt noch motor.schalten haben will macht das per struct.
Mit solchen Lösungen hatte ich früher reichlich zu tun und das waren die
häufigsten Fehlerquellen weil der 'Progger' (kindische Bezeichnung) zu
faul war Indizes oder Pointer zu überprüfen was bei dieser Lösung nötig
ist. Bei C++ geht man vom Design her anders ran weil die Sprache es
hergibt und man nicht mit Compiler Klimmzügen Sicherheit reinbringen
muss.
Beim Beispiel mit dem Motor brauche ich keine Abstraktion, der motor ist
ein DigitalOut (im einfachen Fall) und ein 'motor = 1' schaltet den ein.
So ein Motor ist in der realen embedded Welt aber nicht so einsam und
gehört zu einer Baugruppe die einen Namen und Funktionen hat und
wunderbar als Objekt abgebildet werden kann, zB eine Antriebsachse mit
Endschaltern. Das packt man mit den nötigen Funktionen und internen
Zuständen in eine Klasse. Und wenn man mehrere Achsen hat sind es ganz
einfach mehrere Instanzen.
Die Ersatzlösung in C die das nachbilden möchte muss jetzt ein Argument
für die Achsenauswahl in jede Funktion bekommen. Ein einfacher
numerischer Index ist kein sicherer Typ und man kann nur den
Wertebereich auf Gültigkeit prüfen. Pointer oder Handles sind
aufwändiger und evtl. noch gefährlicher bei falscher Benutzung.
Alleine diese Kapselung ist für mich Grund genug lieber mit C++ zu
arbeiten, auch und gerade auf µCs weil sich die Komponenten gut in
Objekten abbilden lassen. Und solange ich eine konrete Anwendung habe
brauche ich keine wilden Abstraktionen.
Vincent H. schrieb:> X4Ubbb schrieb:>> Wenn irgendein depp (üblicherweise der Autor) die Ports über andere>> Routinen toggelt und dabei statis ignoriert bzw falsch setzt schießt er>> die schöne Steuerung nebst Motor. Ohne das ich was machen kann. Wie>> auch? Weder µP-Hardware noch der Compiler können das verhindern.>>>> Das geht nur in Hardware, Thema Eigensicherheit (die zu der Liste der>> Aufgaben noch dazu kommt).>>>> Fazit bisher: OOP auf'm uC ist ein Randproblem.>> Genau aus dem Grund haben Klassen private und public Member. Ein Motor> Pin ist ein internes Detail des Motors und damit von außen nicht> zugreifbar.
Leider ist das der Peripherie des µC egal. Der µC führt ja bloß den
Binärcode aus, der auf Register zugreift. Wenn also ein Timer von
anderer Stelle so konfiguriert wird, dass er ne PWM auf dem selben Pin
aktiviert oder ein UART dahinter angeschaltet wird, dann hat man
konkurrierenden zugriff auf den Pin. Davor schützen weder Klassen noch
Compiler, da die nichts von der "Verdrahtung" der Register mit der
Hardware wissen. Es könnten auch zwei Klassen den selben Pin als GPIO
verwenden. Das macht im Normalfall natürlich keiner, aber wenn sich der
Fehlerteufel einschleicht, kann sowas schon mal passieren. Icdh denke in
diese Richtung hat das abgezielt.
X4Ubbb schrieb:> Nicht ganz so hübsch zu schreiben, dafür spart man sich eine Menge> undurchsichtiger Zusammenhänge.
Für wen undurchsichtig? Wahrscheinlich für jemanden ohne C++ Erfahrung.
X4Ubbb schrieb:> Fazit bisher: OOP auf'm uC ist ein Randproblem.
Als Problem würde ich das eher nicht sehen. Es ist eher bei weitem noch
nicht so oft vertreten wie klassische Ansatz, aber das kann sich ja noch
ändern.
Wilhelm M. schrieb:> std::iota(std::begin(c), std::end(c), 42);
Geht das wirklich bei avr mit Standardmitteln?
Grüße Oliver
Oliver J. schrieb:>> Wilhelm M. schrieb:>> std::iota(std::begin(c), std::end(c), 42);> Geht das wirklich bei avr mit Standardmitteln?
Selbstverständlich.
Was sollte das auch mit C++ als Sprache zu tun haben?
Allerdings ist beim avr-g++ die Libstdc++ nicht dabei (aus gutem Grund
;-).
Aber man kann natürlich die benötigten templates wie std::iota und
std::begin oder std::end oder auch std::array sich gerade selbst
schreiben (oder aus der Libstdc++ abkupfern, wenn man das nicht
will/kann). std::vector vllt eher nicht, da man hier dann dyn.
Allokation braucht.
Wilhelm M. schrieb:> Selbstverständlich.> ..> Aber man kann natürlich die benötigten templates wie std::iota und> std::begin oder std::end oder auch std::array sich gerade selbst> schreiben ...
Man kann nicht, man muß. Was die Definition von "selbstverständlich"
dann doch etwa relativiert.
Aber die Welt besteht zum Glück nicht nur aus AVRs.
Oliver
Oliver S. schrieb:> Wilhelm M. schrieb:>> Selbstverständlich.>> ..>> Aber man kann natürlich die benötigten templates wie std::iota und>> std::begin oder std::end oder auch std::array sich gerade selbst>> schreiben ...>> Man kann nicht, man muß. Was die Definition von "selbstverständlich"> dann doch etwa relativiert.
... das wäre dann eine gute Fingerübung für die, die hier mitreden
wollen ;-)
>> Aber die Welt besteht zum Glück nicht nur aus AVRs.
Genau!
Wilhelm M. schrieb:> Allerdings ist beim avr-g++ die Libstdc++ nicht dabei (aus gutem Grund> ;-)
Und was ist dieser Grund?
Du als C++ Hero wärst doch prädestiniert, den GCC so zu pimpen, dass er
auch C++ unterstützt.
Johann L. schrieb:> Wilhelm M. schrieb:>> Allerdings ist beim avr-g++ die Libstdc++ nicht dabei (aus gutem Grund>> ;-)>> Und was ist dieser Grund?>> Du als C++ Hero wärst doch prädestiniert, den GCC so zu pimpen, dass er> auch C++ unterstützt.
Was meinst Du? Tut er doch, der avr-g++. Sogar z.B. __underlying_type
funktioniert ...
Nur - wie gesagt - die dynamischen Container der Libstdc++ machen auf
diesen kleinen µC wenig bis gar keinen Sinn. Auf anderen dagegen schon.
Wilhelm M. schrieb:> Johann L. schrieb:>> Wilhelm M. schrieb:>>> Allerdings ist beim avr-g++ die Libstdc++ nicht dabei (aus gutem Grund>>> ;-)>>>> Und was ist dieser Grund?>>>> Du als C++ Hero wärst doch prädestiniert, den GCC so zu pimpen, dass er>> auch C++ unterstützt.>> Was meinst Du? Tut er doch, der avr-g++.
Nein, tut er nicht, denn zum C++ Standard gehören eben auch die
Standard-Bibliotheken, nicht nur der Sprachkern.
Und den "guten Grund", warum dieser Teil von C++ nicht unterstützt wird,
kenn ich auch nicht.
Johann L. schrieb:> Wilhelm M. schrieb:>> Johann L. schrieb:>>> Wilhelm M. schrieb:>>>> Allerdings ist beim avr-g++ die Libstdc++ nicht dabei (aus gutem Grund>>>> ;-)>>>>>> Und was ist dieser Grund?>>>>>> Du als C++ Hero wärst doch prädestiniert, den GCC so zu pimpen, dass er>>> auch C++ unterstützt.>>>> Was meinst Du? Tut er doch, der avr-g++.>> Nein, tut er nicht, denn zum C++ Standard gehören eben auch die> Standard-Bibliotheken, nicht nur der Sprachkern.
Stimmt! Ich sprach aber oben von avr-g++ und nicht vom gesamten
C++-Standard.
> Und den "guten Grund", warum dieser Teil von C++ nicht unterstützt wird,> kenn ich auch nicht.
Der Grund ist m.E. zweiteilig:
1) Allgemeine, unbeschränkte dyn. Container machen auf so kleinen µCs
keinen Sinn.
2) Mit 1) hätten wir dann nur eine "halbe" Libstdc++, was auch keinen
Sinn macht.
Aber: mit Bibliotheken wie z.B. ETL ist diese halbe Libstdc++ ja
verfügbar.
Wilhelm M. schrieb:> Allerdings ist beim avr-g++ die Libstdc++ nicht dabei (aus gutem Grund> ;-).
Es gibt keinen „guten Grund“, höchstens einen schlechten: es hat
sich lange Zeit kein Schwein drum gekümmert, das mal zum Laufen zu
bekommen. Offensichtlich war der Leidensdruck für die C++-Nutzer nicht
sehr hoch, vollständige Sprachunterstützung auch aus Bibliothekssicht
zu haben. Vor einiger Zeit gab es mal wieder eine Initiative, es
wurde auch bisschen was dafür in der regulären avr-libc angepasst,
aber seither ist es wieder ruhig geworden darum. Keine Ahnung, wie
der aktuelle Stand da wirklich ist.
Ob nun dynamische Objekte Sinn haben oder nicht, braucht man nicht
aus Sicht der Sprache zu argumentieren: dynamische Probleme lassen
sich nur dynamisch lösen, für statische Probleme wiederum zieht man
sich das sowieso nicht rein. Die C-Bibliothek hat ja auch ein malloc(),
das beißt keinen in die Wade, solange man es nicht benutzt …
Wieso wird hier über Abstraktion geschimpft bzw. solche Codebeispiele
gegenüber gestellt. Jeder von euch nutzt doch C oder C++ oder sonst was
zum Programmieren, das ist schon eine enorme Abstraktion im Vergleich
zum Assembler. Von fertigen Bibliotheken will ich gar nicht anfangen zu
reden und dennoch nutzt jeder sie gerne.
Sinn und Zweck einer Sprache muss es sein Dinge einfacher und
effizienter zu machen und das tut C++ nun mal besser als C usw. Beide
Sprachen sind aber nah genug an der Hardware weil es eben kompilierte
Sprachen sind.
Die Tragweite der Abstraktion im Programm selbst hat man doch selbst in
der
Hand, in C++ muss ich nicht zwingend Klassen verwenden, C++ bietet mir
lediglich die Möglichkeit, ich kann aber 100% prozedural wie in C auch
coden wenn ich will. Aber Klassen mit Vererbung, Nutzung gleicher
Funktionsnamen und Variablen in verschiedenen Klassen ist schon eine
tolle Sache um ein paar Dinge zu nennen.
christopher robin schrieb:> Wieso wird hier über Abstraktion geschimpft bzw. solche Codebeispiele> gegenüber gestellt. Jeder von euch nutzt doch C oder C++ oder sonst was> zum Programmieren, das ist schon eine enorme Abstraktion im Vergleich> zum Assembler. Von fertigen Bibliotheken will ich gar nicht anfangen zu> reden und dennoch nutzt jeder sie gerne.
Ja. Man möchte nicht jedes Mal wieder eine neue Tastaturabfrage oder ein
Protokoll neu aufbauen. Heute muss ich sagen: Gottseidank haben
ich/wir damals die Hardware in unseren Bibliotheken abstrahiert. Nur so
war der Umzug von AVR auf deutlich leistungsfähigere STM32 ohne größere
Schmerzen möglich.
Fortschritt in der IT lebt davon, dass wir mehr und mehr abstrahieren -
schon weil der Mensch gar nicht in der Lage ist, beliebige viele Dinge
gleichzeitig zu überblicken.
> Die Tragweite der Abstraktion im Programm selbst hat man doch selbst in> der> Hand, in C++ muss ich nicht zwingend Klassen verwenden, C++ bietet mir> lediglich die Möglichkeit, ich kann aber 100% prozedural wie in C auch> coden wenn ich will. Aber Klassen mit Vererbung, Nutzung gleicher> Funktionsnamen und Variablen in verschiedenen Klassen ist schon eine> tolle Sache um ein paar Dinge zu nennen.
Das habe ich als Argument lange vermisst: C++ ist doch eine Obermenge
von C.
C++ enthält ja nicht nur OOP-Komponenten, sondern eben auch andere
schöne Dinge (Namespaces usw.), die man auch bei rein prozeduraler
Programmierung nutzen kann.
Jörg W. schrieb:> Wilhelm M. schrieb:>> Allerdings ist beim avr-g++ die Libstdc++ nicht dabei (aus gutem Grund>> ;-).>> Es gibt keinen „guten Grund“, höchstens einen schlechten: es hat> sich lange Zeit kein Schwein drum gekümmert, das mal zum Laufen zu> bekommen. Offensichtlich war der Leidensdruck für die C++-Nutzer nicht> sehr hoch, vollständige Sprachunterstützung auch aus Bibliothekssicht> zu haben.
M.E. ist eine 100%-Kompatibilität zur STL auch nicht erstrebenswert für
die Anwendung auf so kleine µC wie die AVRs. Das fängt mit dynamischen
Containern an, die nicht unkontrolliert wachsen dürfen. In der STL gibts
eine Exception (auch die wollen wir auf µC nicht), wenn der Allokator
kein Element mehr allokieren kann. Verändert man die Semantik des
Containers zu einem mit begrenzter Kapazität und will man keine
Exception, muss man an der Schnittstelle bspw. von std::vector was
verändern, etwa void push_back() zu bool push_back(), oder auch void
pop_front() zu bool pop_front(), da man auch kein UB akzeptieren kann.
Und wenn man nicht 100% kompatibel sein kann, sollte man es lassen. Das
ist m.E. ein guter Grund. Und deswegen wird sich da auch keiner drum
kümmern. Und wie gesagt: es gibt ja bereits Ersatz ...
Chris D. schrieb:>> Das habe ich als Argument lange vermisst: C++ ist doch eine Obermenge> von C.
Leider nicht. Es gibt filigrane Unterschiede z.B. wann UB entsteht. Der
Klassiker sind unions und der Zugriff auf das "non-active-member".
> C++ enthält ja nicht nur OOP-Komponenten, sondern eben auch andere> schöne Dinge (Namespaces usw.), die man auch bei rein prozeduraler> Programmierung nutzen kann.
Das ist doch gerade das, was ich hier schon zum x-ten Mal sage: C++ ist
eine Multiparadigmen-Sprache (vielleicht weiß ja keiner was damit
gemeint ist ...): also mindestsmal kann ich auch nur rein prozedural und
imperativ programmieren. Man muss ja nicht gleich eine DSL (nein: nicht
Telekom ...) implementieren oder funktional werden.
Und es gibt eben so viele kleine Nettigkeiten, die wirklich Freude
machen: zuallererst domänenspezifische Datentypen etwa Länge[m],
Strom[A], Ladung[C], ...
Jörg W. schrieb:>> Objektorientierte Programmierung ist eine Domäne von äußerlich gleichen,>> aber innerlich verschiedenen Dingen>> … also beispielsweise einem Dutzend Motoren eines Roboters? ;-)
Genau. Und bei jedem Motor ist die Ansteuerung ganz anders, weswegen
jeder seine eigenen Methoden haben muß und sich keinerlei Gemeinsamkeit
findet, wenn man mal vom Wort "Motor" absieht. Was hast du denn von
einer Klasse "Motor", wenn jeder Motor seinen eigenen Satz Methoden
haben muß? Oder jedem Motor seine eigene Klasse, die mit der des
Nachbarmotors nix zu schaffen hat?
Die Alternative sähe so aus, daß man alles bei allen Motoren
parametrisieren müßte, um auf gleiche Methoden kommen zu können. Aber
das bläht die Treiber derart auf, daß man es lieber bleiben lassen
sollte.
W.S.
W.S. schrieb:> Jörg W. schrieb:>>> Objektorientierte Programmierung ist eine Domäne von äußerlich gleichen,>>> aber innerlich verschiedenen Dingen>>>> … also beispielsweise einem Dutzend Motoren eines Roboters? ;-)>> Genau. Und bei jedem Motor ist die Ansteuerung ganz anders, weswegen> jeder seine eigenen Methoden haben muß und sich keinerlei Gemeinsamkeit> findet, wenn man mal vom Wort "Motor" absieht. Was hast du denn von> einer Klasse "Motor", wenn jeder Motor seinen eigenen Satz Methoden> haben muß? Oder jedem Motor seine eigene Klasse, die mit der des> Nachbarmotors nix zu schaffen hat?>> Die Alternative sähe so aus, daß man alles bei allen Motoren> parametrisieren müßte, um auf gleiche Methoden kommen zu können. Aber> das bläht die Treiber derart auf, daß man es lieber bleiben lassen> sollte.>> W.S.
Nur weil ein Schritt- und ein Encoder-gestützer DC Motor andere
Ansteuerungen haben, heißt das doch noch lange nicht, dass ich nicht
beide mit "Motor::move(x, y, z)" ansprechen kann... Nona brauch ich für
jeden Motor eine eigene Implementierung. Hab ich die aber einmal fertig,
so ist mir später einmal egal ob ich mit einer move Funktion jetzt den
Schritt- oder den DC Motor steuere.
Bei diesem Komfort hört die objektorientierte Abstraktion aber nicht
auf. Wird das Projekt später einmal erweitert und eine Asynchronmaschine
eingebaut, so zwinge ich mit dem bestehenden Code dem neuen Motor exakt
das selbe Interface auf. Damit unterbinde ich von vornherin, dass ein
Kollege aus Jux und Tollerei eine "goto" Funktion mit Polar-Koordinaten
schreibt...
PeterPan schrieb:> Ich frage mich wie du dann überhaupt C in Betracht ziehen kannst, eine> Sprache die nicht mal sowas wie echte Konstanten kennt (const wohl eher> nicht...).
Na, in C++ ist "const" auch nicht das, was man unter "konstant" oder
"read-only" versteht.
Instruktives Beispiel:
D.h. es wird ein statischer Konstruktor aus dem Hut gezaubert (geht halt
nicht anders) um i zu initialisieren.
Wenn du nun PROGMEM verwendest weil du glaubst, const i sei read-only:
BUMMER.
Natürlich ist i read-only auf C++ Ebene, aber eben nicht read-only auf
Binärebene, und C++ kann offenbar auch keine solche Zusicherung
machen.
Dafür brauchte es dann eine Spracherweiterung wie constexpr. Welche
Zusicherungen die auf Binärebene macht weiß ich nicht. Da der
Standard darüber (z.B. elf oder aout) keine Annahmen macht, stell ich
mir die Spezifikation ganz interessant vor...
Wenn es hingegen in C übersetzt, dann ist es read-only auf Binäreben
-- und wenn es nicht übersetzt, bedeutet das nicht, dass es im
landläufigen Sinne nicht konstant ist. Ist zwar eine Einschränkung in
C, dafür weiß ich aber was rauskommt wenn es compiliert.
Wilhelm M. schrieb:> Johann L. schrieb:>> PeterPan schrieb:>>> Ich frage mich wie du dann überhaupt C in Betracht ziehen kannst, eine>>> Sprache die nicht mal sowas wie echte Konstanten kennt (const wohl eher>>> nicht...).>>>> Na, in C++ ist "const" auch nicht das, was man unter "konstant" oder>> "read-only" versteht.>> Oft ein Mißverständis, was const bedeutet!
Ich wollte nur darauf hingewiesen haben, dass const in C++ eben nicht
konstant ist von der binär-Perspektive.
Auf einem Host ist mir das relativ Wurscht, aber je limitierter die
Zielplattform ist, desto exakter muss die Code-Erzeugung sein.
Und an C++ für AVR interessiert mich zumindest immer auch welchen Code
der Compiler generiert, einfach weil ich immer auch die Tool-Perspektive
habe.
Johann L. schrieb:>> Oft ein Mißverständis, was const bedeutet!>> Ich wollte nur darauf hingewiesen haben, dass const in C++ eben nicht> konstant ist von der binär-Perspektive.
Die C++-Konstanten sind i.Allg. schon etwas konstanter als diejenigen in
C, aber noch lange nicht konstant genug, um bspw. folgendes zu machen:
Ich will unbedingt noch in Errinnerung rufen, dass der erste C++
Compiler CFront (und folgende andere frühe C++ Compilers) nicht direkt
Maschinencode sondern C-Code generierten.
Für die Zankdiskussion hier: man schrieb in C Quellcode, mit dem man
nichts zu tun haben wollte.
Man wendet in der neuen Sprache C++ "ach so fürchterliche" OO-Konzepte
an, welche sich in der alten Sprache C zwar auch formulieren lassen,
aber in einer (wegen C) gezwungenermassen dermassen schrecklichen Form
ausfallen, auf die man keine Lust hat sie von Hand weder direkt so
hinzuschreiben noch zu warten, sodass eben einen Automaten (der
Umsetzer) ran muss damit man nur in der "weniger schrecklichen" neuen
Formulierung bleiben kann.
Das ist übrigens eine sehr verbreitete Vorgehensweise, welche bewährt
und akzeptiert ist.
Nicht nur dass "damals" aus dem Verdruss in Assembler/Maschinensprache
immer wieder den selben boilerplate Code durchkauen zu müssen
BASIC/FORTRAN/C/usw. entstanden -alle diese Programmiersprachen haben
ziemlich überschaubare "schlimme" aber eben doch sinnvollerweise nötige
Abstraktionen resp. Äquivalenzen zu was man davor machte- sondern auch
neuzeitigere, junge, "ach so fürchterlich Abstrakte" Programmiersprachen
lassen sich so umsetzen: p2c, ptoc, Ofront, m2c, GNAT, u.v.A.mehr. Auch
für die JVM gibt es mehr als bloss Java. LLVM lässt ebenso grüssen.
Letztendlich werden alle Programme, egal in welcher Sprache sie
geschrieben sind, von den CPUs in Maschinencode ausgeführt.
C-isten, welche C++-isten den nächsten "fürchterlichen" Schritt richtung
Hochsprache wegzetern wollen sollen bedenken dass sie selbst auf dem
gleichen Gefälle und in gleicher Position gegenüber ASM-isten sind.
Für alle 3 genannten Gruppen gilt: jeder darf bei seinem
Lieblingswerkzeug bleiben, solange es ihm gut von der Hand geht und egal
wie umständlich es tatsächlich ist (mit Computern werden Probleme
gelöst, welche man ohne Computer gar nicht hat). Das ist in beide
Blickrichtungen des o.g. Gefälles gültig was auch impliziert das es noch
handlicheres über C++ gibt.
Alle möglichen "Zwischenformen" eines Computerprogrammes verlieren
sowieso beim Relevanzvergleich (pun intended): es zählen letztlich nur
die "niedrigste" Form für den ausführen Automaten und die dem die
ursprüngliche Aufgabe lösenden Mensch behaglichste Form. Erstere Akteure
haben nichts zu melden weil leblose Maschinen - letztere sind aus
biologischer Evolution hervorgegangene komplexe Organismen wovon leider
nicht alle Exemplare nur die besten Vorteile mit auf den Lebensweg
abbekommen haben. In dieser Erbsenklauberei um den blossen Unterschied
von "++" im Namen kann man schon merken wer sich mit Besitzstandwahrung
begnügt und wer weder tief noch weit genug über seinen Tellerrand zu
Blicken bereit/fähig ist.
Programmiersprachentheaterintendant schrieb:> C-isten, welche C++-isten den nächsten "fürchterlichen" Schritt richtung> Hochsprache wegzetern wollen sollen bedenken dass sie selbst auf dem> gleichen Gefälle und in gleicher Position gegenüber ASM-isten sind.> Für alle 3 genannten Gruppen gilt: jeder darf bei seinem> Lieblingswerkzeug bleiben, solange es ihm gut von der Hand geht und egal> wie umständlich es tatsächlich ist (mit Computern werden Probleme> gelöst, welche man ohne Computer gar nicht hat). Das ist in beide> Blickrichtungen des o.g. Gefälles gültig was auch impliziert das es noch> handlicheres über C++ gibt.
Ja, da hast Du sicherlich recht. Und bei den C++-isten führt das dann
dazu, dass man sich eine eDSL in C++ selbst für seine Anwendungsdomäne
erzeugt, die natürlich noch abstrakter als C++ selbst. Generische
Algorithmen sind die Vorstufe, eine eDSL die nächste Stufe.
Aber das machen ja auch die C-isten, z.B. mit Regulären-Ausdrücken, was
einer DSL in etwa nahe kommt.
Letztendlich geht es doch darum, wie schnell, sicher, effizient und
dauerhaft ich als Mensch mit meinem Werkzeug ein Problem lösen kann.
Natürlich muss das Ziel dabei sein, dass am Ende ein optimaler Code für
die Maschine dabei heraus kommt.
Was mich bisher von C++ abgehalten hat, wurde durch den Beitrag von
Beitrag "Re: Welche Programmiersprache auf µC"
mehr als deutlich dargelegt.
Allerdings für die kleinen µCs muss ich doch nicht den ganzen Wald
kennen.
Nur wie meine Perlen finden, ohne den gesamten Wald durchforsten zu
müssen?
Johann L. schrieb:> Wilhelm M. schrieb:>> Johann L. schrieb:>>> Wilhelm M. schrieb:>>>> Allerdings ist beim avr-g++ die Libstdc++ nicht dabei (aus gutem Grund>>>> ;-)>>>>>> Und was ist dieser Grund?>>>>>> Du als C++ Hero wärst doch prädestiniert, den GCC so zu pimpen, dass er>>> auch C++ unterstützt.>>>> Was meinst Du? Tut er doch, der avr-g++.>> Nein, tut er nicht, denn zum C++ Standard gehören eben auch die> Standard-Bibliotheken, nicht nur der Sprachkern.>> Und den "guten Grund", warum dieser Teil von C++ nicht unterstützt wird,> kenn ich auch nicht.
So weit ich das verstehe, beruht ein großer Teil der STL auf dem
Vorhandensein von Exceptions. Ohne wäre es eine NonSTL.
Allerdings gibt es es einige "statische" Dinge, z.B. std::array, die man
kostenlos benutzen kann um Arrays konstanter Größe an <allgorithm>
anzudocken. Man muß z.B. die Suche eines Maximalwerts in einem Int-Array
nicht jedesmal neu erfinden. Stattdessen std::foreach über das Array und
Lambda zu Verarbeitung der Werte.
Teo D. schrieb:> Nur wie meine Perlen finden, ohne den gesamten Wald durchforsten zu> müssen?
Eigentlich nur, indem du überhaupt erstmal anfängst, dich damit zu
befassen.
Ich stand letztens auch vor rätselhaftem C++-Code (in qucs), bei dem
ich erstmal Tante Gugel angeworfen habe. Offenbar versucht man in
den letzten C++-Standards, FORTRAN nun mal die Domäne streitig zu
machen, sie hätten für parallele Vektordatenverarbeitung die einzige
nutzbare Programmiersprache, und hat solche Dinge jetzt in C++ drin.
Allerdings genügte es mir für diesen Zweck vollauf, nur die Grundidee
hinter den paar Zeilen zu verstehen (irgendwas musste da geändert
werden, weiß nicht mehr genau, was es war). Solche Dinge muss ich
gewiss nicht selbst schreiben können, erst recht nicht auf einem
kleinen Controller.
Es gibt allerdings ein ziemlich gewichtiges Argument für C:
Es kam bei Star Wars zum Einsatz und zwar bei der Programmierung des
Todessterns. Ich kann auch gerne die Quelle benennen :-)
Yalu X. schrieb:> Die C++-Konstanten sind i.Allg. schon etwas konstanter als diejenigen in> C, aber..
Muß ich jetzt mit dir schimpfen?
Also, was man auf nem µC mit einem ordentlichen Compiler in C const
macht, IST konstant. Und zwar derart konstant, daß es nicht konstanter
geht.
Hab grad mal das Menüsystem der steinalten Lernbetty (per Keil
übersetzt) angeschaut: da ist schlichtweg ALLES im Flash. Sowohl die
structs als auch die Texte als auch die Members in den structs und deren
eben grad vorwärts deklarierten Zielen. Eben alles.
konstanter geht es nicht mehr.
W.S.
Programmiersprachentheaterintendant schrieb:> In dieser Erbsenklauberei um den blossen Unterschied> von "++" im Namen kann man schon merken wer sich mit Besitzstandwahrung> begnügt und wer weder tief noch weit genug über seinen Tellerrand zu> Blicken bereit/fähig ist.
Ach, der Tellerrand.
Ja, da hast du wohl Recht. So ziemlich alle hier in diesem Thread
beteiligten scheinen einen recht kleinen Teller zu haben, der mal gerade
von Assembler (schon außerhalb des Tellerrandes) über C und dann C++ bis
Java und C# reicht. Mir ist dieser Teller zu klein, ich benutze dort wo
ich kann, lieber Pascal und schreibe meine PC-Programme mit Delphi oder
Lazarus. Aber bei den C-lastigen Leuten hier erwarte ich nicht, daß sie
das aktuelle D10.2 Tokyo auch nur vom Hörensagen her kennen.
Nur der Umstand, daß es keinen guten Pascal-Compiler für aktuelle µC
gibt, führt dazu, für µC eben C zu verwenden. Aber auf die Idee, hier
ein C++ verwenden zu wollen, halte ich für reinen Mutwillen.
ach was.. gut Nacht zusammen!
W.S.
W.S. schrieb:> Yalu X. schrieb:>> Die C++-Konstanten sind i.Allg. schon etwas konstanter als diejenigen in>> C, aber..>> Muß ich jetzt mit dir schimpfen?
Nein, musst du nicht. Es sei denn, du kannst nicht anders ;-)
> Also, was man auf nem µC mit einem ordentlichen Compiler in C const> macht, IST konstant. Und zwar derart konstant, daß es nicht konstanter> geht.
Mit konstant meine ich konstant im Sinn einer "constant expression"
gemäß der C- und C++-Norm. Hätte ich konstant im mathematischen Sinn
gemeint, hätte ich das Adjektiv nicht im Komparativ gebraucht.
W.S. schrieb:> Mir ist dieser Teller zu klein, ich benutze dort wo ich kann, lieber> Pascal und schreibe meine PC-Programme mit Delphi oder Lazarus.
Also einfach einen anderen Teller. Auf die Argumente, dass dieser
Teller auch nur golden aussieht und nicht aus Gold ist, gehst du
sowieso nie ein, ist also auch egal. ;-)
Viel mehr schade finde ich eigentlich, dass Ada in der Controllerwelt
nie recht zum Fliegen gekommen ist. Der Zug ist aber halt einfach
abgefahren, wie's scheint.
Wenn die Controller größer werden, kann man sich dann wohl besser
Gedanken um Lua oder Python machen, statt Ada oder Pascal nachzutrauern.
W.S. schrieb:> Also, was man auf nem µC mit einem ordentlichen Compiler in C const> macht, IST konstant. Und zwar derart konstant, daß es nicht konstanter> geht.>> Hab grad mal das Menüsystem der steinalten Lernbetty (per Keil> übersetzt) angeschaut: da ist schlichtweg ALLES im Flash.
Wieso das denn? Was konstant ist, kennt der Compiler, das muss
doch nicht ins Flash?
Jörg W. schrieb:> Wenn die Controller größer werden, kann man sich dann wohl besser> Gedanken um Lua oder Python machen, statt Ada oder Pascal nachzutrauern.
Gedanken kann man sich machen und ein paar nette Spielereien sind damit
sicher auch möglich aber dann hört es da meiner Meinung nach auch schon
auf. Das einzige was noch einigermaßen erträglich vom
Ressourcenverbrauch ist, ist wenn man vorhandene Funktionen, die in C
implementiert sind per Scriptsprache mal eben schnell zu einem Programm
zusammenhackt.
Von der Performance abgesehen mag ich ganz prinzipiell kompilierte
Sprachen, weil der Compiler mir gegebenenfalls schon auf die Finger haut
bevor das Programm läuft. Aus dem Grund ziehe ich mittlerweile auf dem
PC auch ein Go-Programm einem Python-Script vor. Die Programmierung ist
ähnlich dynamisch wie bei Python und Go kompiliert dermaßen schnell das
ein "go run some_program.go" gefühlt genauso schnell startet wie ein
"./python_script.py". Das es zusätzlich noch um ein bis zwei
Größenordnungen schneller läuft interessiert mich dabei meistens nicht
mal, weil der Rechner für ein billiges Script bzw. Progrämmchen meist
ohnehin mehr als genug Dampf hat.
Da aber die aber die Mikrocontroller auch übermorgen nicht dutzende MB
SRAM für lau mit an Board haben werden, wage ich mal zu behaupten, dass
auch in zehn Jahren noch ASM, C und C++ vorherrschen werden (im Bereich
uC). Eventuell wird sich noch Rust einen Teil vom Kuchen schnappen.
Potential hat Rust meiner Meinung nach mehr als genug. Sehr starke
Typisierung, quasi kein Runtime-Overhead und auch kein GC, trotzdem
keine Dangling-Pointer und sonstige Zombies. Der Einstieg ist aber
wirklich hart. Der Compiler haut einem anfangs so oft auf die Finger das
es schnell weh tut. Wer da aus dem C(++)-Lager kommt verliert da schnell
die Lust daran, weil nicht mehr alles erlaubt ist. Dafür hat man eine
gewisse Sicherheit, dass das Programm sofern es denn kompiliert, frei
von irgendwelchen Memorybugs ist.
Christopher J. schrieb:> Jörg W. schrieb:>> Wenn die Controller größer werden, kann man sich dann wohl besser>> Gedanken um Lua oder Python machen, statt Ada oder Pascal nachzutrauern.>> Gedanken kann man sich machen und ein paar nette Spielereien sind damit> sicher auch möglich aber dann hört es da meiner Meinung nach auch schon> auf. Das einzige was noch einigermaßen erträglich vom> Ressourcenverbrauch ist, ist wenn man vorhandene Funktionen, die in C> implementiert sind per Scriptsprache mal eben schnell zu einem Programm> zusammenhackt.
Ich hab leider mit Python und JS auf µC überhaupt keine Erfahrung, kenne
aber zumindest Lua. Wenn ich ein Projekt vorgesetzt bekäme, dass weder
Echtzeitfähigkeit benötigt, noch der Preis auf die letzte Kommastelle
berücksichtigt werden muss, dann würde ich keine Sekunde zögern und
irgendeine Skriptsprache verwenden.
Gerade letzteres, sprich Lua, ist sogar begrenzt Echtzeitfähig, da die
GC bei Bedarf deaktiviert werden kann...
Jörg W. schrieb:> Eigentlich nur, indem du überhaupt erstmal anfängst, dich damit zu> befassen.
Danke, sehr nett von dir mir das zu unterstellen..... :´(
Komm mal wieder runter! (und schneide dir mal wieder die Nasenhaare)
Vincent H. schrieb:> Ich hab leider mit Python und JS auf µC überhaupt keine Erfahrung, kenne> aber zumindest Lua. Wenn ich ein Projekt vorgesetzt bekäme, dass weder> Echtzeitfähigkeit benötigt, noch der Preis auf die letzte Kommastelle> berücksichtigt werden muss, dann würde ich keine Sekunde zögern und> irgendeine Skriptsprache verwenden.> Gerade letzteres, sprich Lua, ist sogar begrenzt Echtzeitfähig, da die> GC bei Bedarf deaktiviert werden kann...
Ja, so waren damals auch meine Gedanken, als ich mir einen passenden
Tcl-Interpreter für AVRs baute: für über 90% der Dinge, die ein µC
macht, reicht eine Skriptsprache (sogar ohne Bytecompiler) vollkommen
aus. Und da Speicher damals knapp war, konnte man so sehr große
Programme z.B. in EEPROMs auslagern.
Lua ähnelt Tcl von der Intention her stark (Einbau in C-Programme,
Verlagerung der laufzeitunkritischen Dinge in die Skriptsprache, sehr
einfache Implementierbarkeit weiterer C-Funktionen). Echtzeitfähigkeit
kann man dort auch sehr einfach nachrüsten.
Heutzutage passt der komplette Interpreter auf einen STM32 und wir sind
dabei, Tk soweit zu portieren, dass man den Großteil der Tk-Befehle dann
für eigene Oberflächen verwenden kann.
Debugging gestaltet sich bei Skriptsprachen auch einfacher, weil man das
Programm in einer Shell direkt auf dem Zielsystem ändern kann.
Bei der Frage "Welche Programmiersprache auf µC" sollte man also
durchaus auch einen interessierten Blick auf Skriptsprachen werfen.
Chris D. schrieb:> Bei der Frage "Welche Programmiersprache auf µC" sollte man also> durchaus auch einen interessierten Blick auf Skriptsprachen werfen.
Wenn man diese Skriptspache erst selbst implementieren muss, dann dürfte
das deren Attraktivitat etwas dämpfen...
Nur weil es damals so war, muss das nicht heute gelten :-)
Es gibt durchaus einige sehr kleine Tcl-Interpreter:
http://wiki.tcl.tk/1363
Bei Lua kenne ich mich nicht aus, aber eLua scheint in die Richtung zu
gehen:
http://www.eluaproject.net/
Jörg W. schrieb:> Teo D. schrieb:>> Nur wie meine Perlen finden, ohne den gesamten Wald durchforsten zu>> müssen?>> Eigentlich nur, indem du überhaupt erstmal anfängst, dich damit zu> befassen.
OK, hab nun auch wieder Bodenkontakt ;)
Hab mich vor ~25J mal damit befasst, daher auch der Bammel :)
C++ Bücher gibt's ja zur genüge.... Leider alles nur Pfade durch den
Dschungel.
Ich würde mir gerne einen Überblick verschaffen, ohne ein Dutzend Bücher
durchforsten zu müssen!
Für die Grundlagen hab ich schon ein halbes Dutzend geordert. Wird schon
was brauchbares dabei sein. :)
Teo D. schrieb:> Hab mich vor ~25J mal damit befasst, daher auch der Bammel :)
Irgendwann damals hatte ich auch mal einen C++-Lehrgang. Zum Glück
nicht alles vergessen. ;-)
Ich würde an deiner Stelle nicht zu viele Bücher wälzen, sondern mal
kleine Stückchen programmieren. Wirklich erstmal die LED als Objekt.
Vielleicht nach dem Ein- und Ausschalten dann auch mit einem
PWM-Timer für die Helligkeitssteuerung erweitern.
AVRs sind aufgrund ihrer IO-Registerstruktur nicht ganz so nett für
eine Abstraktion in C++. Ich schrieb's oben schon mal, ein GPIO wird
dort ja durch das Tupel {PORTx, PINx, DDRx} beschrieben, formal (wenn
man nicht irgendwelche Mystik mit hin- und her-casten und händischen
Offsets zwischen diesen Registern verzapfen möchte) muss man daher
beim Konstruktor alle drei Namen übergeben. Wenn man nur „A“ oder
„B“ (für das Metazeichen „x“ oben) schreiben will, dann hilft da leider
wieder nur der leidige Präprozessor, denn er ist in der Lage, ganze
Bezeichner durch Verkettung zu synthetisieren.
Bei den Cortex-Ms ist das besser, da hat man sowas wie PORTx->DIR
oder PORTx->DIN oder dergleichen; hier kann man das komplette „PORTx“
übergeben und damit den passenden Port wählen.
Achso, und natürlich immer den generierten Assemblercode mit ansehen.
Was sich übrigens gut macht ist, wenn man so viel wie möglich in der
Abstraktion als „const“ bzw. „constexpr“ deklariert. Damit ist dann
verbunden, dass man bei der Instanziierung keine Zuweisung machen
darf, sondern den tatsächlichen Wert über den Konstruktor übergibt.
Das wiederum gibt dem Compiler die Gewissheit, dass die Sache bereits
zur Compilezeit bekannt ist, sodass er maximal die konstanten Dinge
ersetzen kann. Auf dem AVR landet man so am Ende auch wieder bei
CBI und SBI im generierten Code.
Jörg W. schrieb:> Ich würde an deiner Stelle nicht zu viele Bücher wälzen, sondern mal> kleine Stückchen programmieren. Wirklich erstmal die LED als Objekt.> Vielleicht nach dem Ein- und Ausschalten dann auch mit einem> PWM-Timer für die Helligkeitssteuerung erweitern.
Würdest du damit bitte aufhören! :)
Jörg W. schrieb:> AVRs sind aufgrund ihrer IO-Registerstruktur nicht ganz so nett für> eine Abstraktion in C++........
Genau solch Informationen suche ich, kompakt und nicht auf Tausende
Seiten verteilt.....
Jörg W. schrieb:> Ich würde an deiner Stelle nicht zu viele Bücher wälzen
Hab sie ja nur gekauft!
Bei Bücherbillig für 20€. Gelesen wird nur was brauchbar ist. :)
Chris D. schrieb:> Bei der Frage "Welche Programmiersprache auf µC" sollte man also> durchaus auch einen interessierten Blick auf Skriptsprachen werfen.
Ich entwickle gerade eine Interpretersprache NIC für 32-Bit-µCs. Die
Entwicklung ist zur Zeit noch in der Alpha-Phase, jedoch zeigen erste
Benchmarks, dass der Interpreter etwa doppelt so schnell wie PHP und
5-10 mal so schnell wie Python ist. Sobald ich eine Beta-Version fertig
habe, werde ich sie unter "Projekte und Code" vorstellen.
Die hohe Ausführungsgeschwindigkeit wird dadurch erreicht, dass der Host
das NIC-Script vorcompiliert und dann auf den µC einen
maschinenunabhängigen Objekt-Code hochlädt. Dabei werden dann auch
direkt einige Optimierungen vorgenommen: Zum Beispiel werden konstante
Ausdrücke in Expressions direkt zur Compilezeit ausgerechnet.
Da die Übersetzungszeiten unter einer Sekunde bleiben, merkt man das
überhaupt nicht. Sobald der Objekt-Code hochgeladen wurde, wird er auf
dem µC (im Moment werden STM32F10x und STM32F4xx unterstützt) direkt
ausgeführt. Dadurch verkürzt sich die Entwicklung eigener Programme, da
das zeitaufwendige Flashen komplett entfällt.
NIC-Programme laufen nicht nur auf den STM32, sondern auch unter Windows
und Linux - auch wenn das nur ein Nebeneffekt ist. So kann ich den
NIC-Compiler und NIC-Interpreter direkt auf dem Host testen.
Fortgeschrittene Programmierer können die Sprache durch eigene
C-Funktionen erweitern und diese dann direkt aus dem NIC-Programm
aufrufen. So kann man zeitkritische Routinen in den C-Code verlagern.
Im Moment ist die Sprache noch rein prozedural, Objekt-Orientierung wird
aber noch kommen. Außerdem wird es noch einen weiteren Compiler geben,
der den maschinenunabhängigen Objekt-Code in ein C-Programm umwandelt,
um diesen dann zur NIC-Laufzeitbibliothek dazulinken zu können. Damit
kann man dann nach der Entwicklungsphase das Programm mit annähernd der
Geschwindigkeit laufen lassen, wie auch ein adäquates C-Programm
brauchen würde.
Ich habe auch schon mal begonnen, NIC zu dokumentieren. Diese Doku wird
während der Entwicklung von mir parallel fortgeschrieben, siehe Artikel
NIC.
Teo D. schrieb:> Genau solch Informationen suche ich, kompakt und nicht auf Tausende> Seiten verteilt.....
Da es hier ja insgesamt um Sprachauswahl auf Mikrocontrollern geht,
vielleicht dafür einen neuen Thread öffnen? Dieser hier ufert sowieso
gerade aus.
Frank M. schrieb:> Chris D. schrieb:>> Bei der Frage "Welche Programmiersprache auf µC" sollte man also>> durchaus auch einen interessierten Blick auf Skriptsprachen werfen.>> Ich entwickle gerade eine Interpretersprache NIC für 32-Bit-µCs. Die> Entwicklung ist zur Zeit noch in der Alpha-Phase, jedoch zeigen erste> Benchmarks, dass der Interpreter etwa doppelt so schnell wie PHP und> 5-10 mal so schnell wie Python ist. Sobald ich eine Beta-Version fertig> habe, werde ich sie unter "Projekte und Code" vorstellen.
Das klingt interessant, liest sich aber sehr wie Lua. Inwiefern wird
sich NIC von Lua unterscheiden?
Wilhelm M. schrieb:> nicht angebrachten ... Exceptions
Gerade Exceptions sind in kritischen Umgebungen toll, weil damit keine
"vergessenen Returnwerte" passieren können, und man bei nicht abgefangen
Exceptions einfach das ganze System in einen Fehlerzustand schicken
kann.
Ansonsten kann ich nur Bruce Douglass wiedergeben: meist nimmt man C,
nicht weil es toll ist, sondern weil es funktioniert, überall verfügbar
ist und wesentlich einfacher ist als C++. Bei größeren Projekten kann
eine Teilmenge von C++ Sinn machen, wird aber tlw. von
Zertifizierungsstellen nicht gern gesehen wegen mehr möglicher Dynamik
(je nach verwendeten Features). Bzw. die Zertifizierer sehens nicht
gern, ihre Chefs schon, weil die Anzahl der einzuwerfenden großen
Scheine mit dem Zertifizierungsaufwand steigt.
vn n. schrieb:> Gerade Exceptions sind in kritischen Umgebungen toll, weil damit keine> "vergessenen Returnwerte" passieren können, und man bei nicht abgefangen> Exceptions einfach das ganze System in einen Fehlerzustand schicken> kann.
Leider ist der Overhead nicht ganz zu verachten, den sie erzeugen.
Daher mag man sie im Embedded-Bereich eher nicht. Ansonsten fänden
sie wohl viele Programmierer gut.
> AVRs sind aufgrund ihrer IO-Registerstruktur nicht ganz so nett> für eine Abstraktion in C++.
Offensichtlich hat auch Atmel das inzwischen erkannt und es bei der
Xmega Serie besser gemacht. Für's Hobby sind die allerdings aus anderen
Gründen weniger attraktiv.
Stefan U. schrieb:> Offensichtlich hat auch Atmel das inzwischen erkannt und es bei der> Xmega Serie besser gemacht.
Ich denke nicht, das C++ dort das primäre Argument war :), aber ja,
deren IO-Register sind diesbezüglich besser strukturiert.
> Für's Hobby sind die allerdings aus anderen Gründen weniger attraktiv.
Nicht nur da, auch anderweitig scheinen sie mir nicht so gut „ans
Fliegen gekommen“ zu sein wie die klassischen AVRs. Verglichen mit
der Konkurrenz aus dem Cortex-M-Lager waren sie dafür wohl einfach
zu spät auf dem Markt.
Stefan U. schrieb:>> AVRs sind aufgrund ihrer IO-Registerstruktur nicht ganz so nett>> für eine Abstraktion in C++.>> Offensichtlich hat auch Atmel das inzwischen erkannt und es bei der> Xmega Serie besser gemacht. Für's Hobby sind die allerdings aus anderen> Gründen weniger attraktiv.
Also wie die phys. I/O-Registeranornung aussieht, ist ziemlich egal. Man
braucht ein bißchen template-Mechanik und gut ist.
Das einzige, was sie bei den XMegas besser gemacht habe, dass sie schon
die C structs definiert haben. Die muss man bei den non-XMega eben
selbst erzeugen...
Bei den AVRs hat man vielleicht die IO-Register auf den Befehlssatz hin
optimiert. Häufig bitweise zu Änderndes ganz nach vorn.
Die XMegas haben aber so viele Register, daß der AVR-Ansatz eh nichts
wird. Deshalb ist identischer Aufbau und instanzspezifische Basisadresse
für die einzelnen Geräte sicher auch von Hardwareseite besser zu
handhaben.
Vincent H. schrieb:> Das klingt interessant, liest sich aber sehr wie Lua.
Ich kannte Lua bisher nicht, habe es mir eben mal näher angeschaut. Ja,
das sieht auf den ersten Blick tatsächlich sehr ähnlich aus :-)
> Inwiefern wird sich NIC von Lua unterscheiden?
Das kann ich jetzt noch nicht sagen, da ich Lua überhaupt nicht kenne.
Ich werde mir Lua auf jeden Fall mal näher anschauen. Danke für den
Hinweis.
Ich habe mir eben mal Lua 5.2 unter Linux installiert und das im
NIC-Artikel angeführte Benchmark-Programm "Sieb des Eratosthenes"
von NIC auf Lua umgeschrieben, was gar nicht so viel Umstände bereitete.
Ergebnis: Für die Ermittlung sämtlicher Primzahlen unter 100000 braucht
NIC 5,3 Sekunden und Lua 6,1 Sekunden auf einem i7-2600@3.40GHz. Mit der
um 20% höheren Geschwindigkeit bin ich ganz zufrieden, gerade weil NIC
noch im Alpha-Stadium und als Interpreter trotzdem von der
Ausführungszeit bisher noch ungeschlagen ist. Sobald der
maschinenunabhägige Objekt-Code eines NIC-Programms in C übersetzt und
damit echter Binärcode erzeugt werden kann, sollte noch ein weiterer
Faktor von mindestens 10 drin sein.
Die Lua-Variante vom Sieb-Programm werde ich dann im NIC-Artikel
noch einfügen zum Vergleich.
Wie ich schon sagte: Sobald eine Beta-Version von NIC verfügbar ist,
werde ich das NIC-Projekt incl. Sources veröffentlichen.
Vincent H. schrieb:> Ich hab leider mit Python und JS auf µC überhaupt keine Erfahrung, kenne> aber zumindest Lua. Wenn ich ein Projekt vorgesetzt bekäme, dass weder> Echtzeitfähigkeit benötigt, noch der Preis auf die letzte Kommastelle> berücksichtigt werden muss, dann würde ich keine Sekunde zögern und> irgendeine Skriptsprache verwenden.>> Gerade letzteres, sprich Lua, ist sogar begrenzt Echtzeitfähig, da die> GC bei Bedarf deaktiviert werden kann...
Ja, Lua ist meiner Meinung nach tatsächlich noch die brauchbarste
Skriptsprache für Systeme mit begrenzten Ressourcen, weil vor allem auch
die Anbindung an C sehr einfach ist. Schaut man da allerdings mal bei
den üblichen Verdächtigen, wie z.B. http://nodemcu.com/ in den
Source-Code, stellt man eben auch schnell fest, das im Prinzip alle
"Low-Level" geschichten in C implementiert sind und man mit Lua
lediglich "mal eben schnell" die einzelnen Bausteine zusammensetzt.
Natürlich kann man Skripte vorab zu Bytecode kompilieren und das geht
auch bei MicroPython aber wenn ich mein Programm ohnehin immer neu laden
muss, dann kann ich das doch auch gleich in C++ schreiben (meinetwegen
mit Arduino) und statt einer VM packe ich einfach einen Bootloader auf
den uC der gegebenenfalls auch noch netzwerkfähig ist und wo ich über
ein HTML-Interface eine neue Firmware laden kann, so wie das bei jedem
Router typischerweise ist.
Frank M. schrieb:> Ich entwickle gerade eine Interpretersprache NIC für 32-Bit-µCs.
Sieht, wie ich finde, ebenenfalls sehr interessant aus.
Jörg W. schrieb:> vn n. schrieb:>> Gerade Exceptions sind in kritischen Umgebungen toll, weil damit keine>> "vergessenen Returnwerte" passieren können, und man bei nicht abgefangen>> Exceptions einfach das ganze System in einen Fehlerzustand schicken>> kann.>> Leider ist der Overhead nicht ganz zu verachten, den sie erzeugen.>> Daher mag man sie im Embedded-Bereich eher nicht. Ansonsten fänden> sie wohl viele Programmierer gut.
Auch wenn die Idee hinter den C++-Exceptions löblich sein mag, finde ich
die Umsetzung eher suboptimal. Man hat seine Programmlogik und parallel
dazu eine Exceptionlogik. Tritt ein Fehler auf, muss man aber erstmal
wieder beides synchronisieren. Ich persönlich finde den Ansatz von Go
oder Rust, die Fehlerbehandlung grundsätzlich über die Rückgabewerte der
Funktionen (mittels "multiple return type") durchzuführen, wesentlich
eleganter. Der Programmierer sollte sich eben schon in seiner
Programmlogik Gedanken machen, an welcher Stelle etwas schief gehen kann
und wie er das dann abfängt. In C gibt es ja die Hilfskrücke errno.h
bzw. das überladen von Rückgabewerten (z.B. -1 für einen Fehler bei der
Initialisierung eines Sockets). In der Praxis werden aber häufig nicht
einmal diese Hilfskrücken genutzt und nach dem Motto "Et is noch immer
jot jejange" einfach "void" als Rückgabewert gewählt. Demnach denke ich
ist das grundsätzliche Problem eher die Grundeinstellung des
Programmierers und erst nachrangig das es keine (vom Ressourcenverbrauch
her) brauchbare Möglichkeit gibt Exceptions zu nutzen. Die "multiple
return types" sind im Übrigen quasi ohne Overhead, weshalb man sie mit
Rust auch z.B. auf einem Cortex-M problemlos nutzen kann. Hätte ich für
C einen Wunsch für ein neues Feature frei und zur Wahl stünden
C++-Exceptions und 'multiple return types', so würde ich eindeutig
letzteres bevorzugen.
Christopher J. schrieb:
...
> Jörg W. schrieb:>> vn n. schrieb:>>> Gerade Exceptions sind in kritischen Umgebungen toll, weil damit keine>>> "vergessenen Returnwerte" passieren können, und man bei nicht abgefangen>>> Exceptions einfach das ganze System in einen Fehlerzustand schicken>>> kann.>>>> Leider ist der Overhead nicht ganz zu verachten, den sie erzeugen.>>>> Daher mag man sie im Embedded-Bereich eher nicht. Ansonsten fänden>> sie wohl viele Programmierer gut.
...
> Hätte ich für> C einen Wunsch für ein neues Feature frei und zur Wahl stünden> C++-Exceptions und 'multiple return types', so würde ich eindeutig> letzteres bevorzugen.
In "modernem" C++ werden Exceptions eigentlich kaum noch verwendet.
Besser geeignet sind (v.a. dann auch im µC Bereich) std::optional oder
ähnliche, konkrete Typen, die in ihrem Wertebereich einen ungültigen
Wert haben.
Frank M. schrieb:> Ich habe auch schon mal begonnen, NIC zu dokumentieren. Diese Doku wird> während der Entwicklung von mir parallel fortgeschrieben, siehe Artikel> NIC.
Nochmal vielen Dank für den Hinweis. Das sieht doch schon richtig gut
aus.
Ich werde allerdings bei Tcl bleiben, weil ich gerade die Typlosigkeit
und Flexibilität (vor allem Listen, Arrays, neue Befehle/Umbenennung zur
Laufzeit, Safe-Interpreter usw.) sehr schätze. Die GPIO-Funktionen habe
ich dort schon implementiert ;-)
Und dazu kommt natürlich (hoffentlich in Bälde) die Gui/Tk-Anbindung.
Ich werde das aber auf jeden Fall im anderen Thread
(Beitrag "NIC (aus dem langen C vs C++ Thread)") weiter beobachten.
Jörg W. schrieb:> vn n. schrieb:>> Gerade Exceptions sind in kritischen Umgebungen toll, weil damit keine>> "vergessenen Returnwerte" passieren können, und man bei nicht abgefangen>> Exceptions einfach das ganze System in einen Fehlerzustand schicken>> kann.>> Leider ist der Overhead nicht ganz zu verachten, den sie erzeugen.>> Daher mag man sie im Embedded-Bereich eher nicht. Ansonsten fänden> sie wohl viele Programmierer gut.
Ok, vielleicht muss man der Vollständigkeit halber unterscheiden, ob man
mit 8MHz oder 150MHz unterwegs ist. ;)
Wilhelm M. schrieb:> In "modernem" C++ werden Exceptions eigentlich kaum noch verwendet.> Besser geeignet sind (v.a. dann auch im µC Bereich) std::optional oder> ähnliche, konkrete Typen, die in ihrem Wertebereich einen ungültigen> Wert haben.
In kritischen Umgebungen können Exceptions halt den Vorteil haben, dass
man sie nicht ignorieren kann (im Gegensatz zu return values).
vn n. schrieb:>> Leider ist der Overhead nicht ganz zu verachten, den sie erzeugen.>> Daher mag man sie im Embedded-Bereich eher nicht. Ansonsten fänden>> sie wohl viele Programmierer gut.>> Ok, vielleicht muss man der Vollständigkeit halber unterscheiden, ob man> mit 8MHz oder 150MHz unterwegs ist. ;)
Ich denke, dass das gar nicht so sehr das Problem ist. Das letzte Mal,
als ich mir das mal angesehen habe, war der Code Bloat, der von der
Einbindung der Exceptions verursacht worden ist, aber immens. Damit
ist es zumindest auf kleineren Controllern außen vor.
Wenn man 1 MB Flash hat, mag die Sache anders aussehen.
Jörg W. schrieb:> vn n. schrieb:>>> Leider ist der Overhead nicht ganz zu verachten, den sie erzeugen.>>> Daher mag man sie im Embedded-Bereich eher nicht. Ansonsten fänden>>> sie wohl viele Programmierer gut.>>>> Ok, vielleicht muss man der Vollständigkeit halber unterscheiden, ob man>> mit 8MHz oder 150MHz unterwegs ist. ;)>> Ich denke, dass das gar nicht so sehr das Problem ist. Das letzte Mal,> als ich mir das mal angesehen habe, war der Code Bloat, der von der> Einbindung der Exceptions verursacht worden ist, aber immens. Damit> ist es zumindest auf kleineren Controllern außen vor.>> Wenn man 1 MB Flash hat, mag die Sache anders aussehen.
Wenn man unbedingt Exceptions verwenden muss / möchte, aber deswegen ein
Problem mit der Wartbarkeit / Lesbarkeit bekommt, könnte man auch über
die sog. "Exploding-Return-Types" nachdenken.
Wilhelm M. schrieb:> Wenn man unbedingt Exceptions verwenden muss / möchte, aber deswegen ein> Problem mit der Wartbarkeit / Lesbarkeit bekommt, könnte man auch über> die sog. "Exploding-Return-Types" nachdenken.
Wie groß ist eigentlich der Code Bloat, wenn man anstelle einer
Exception-Instanz einen einfachen Integer-Fehlercode wirft (soetwas wie
"throw 20;" statt "throw new RuntimeError("bla");")?
Sheeva P. schrieb:> Wilhelm M. schrieb:>> Wenn man unbedingt Exceptions verwenden muss / möchte, aber deswegen ein>> Problem mit der Wartbarkeit / Lesbarkeit bekommt, könnte man auch über>> die sog. "Exploding-Return-Types" nachdenken.>> Wie groß ist eigentlich der Code Bloat, wenn man anstelle einer> Exception-Instanz einen einfachen Integer-Fehlercode wirft (soetwas wie> "throw 20;" statt "throw new RuntimeError("bla");")?
Das sollte man nicht machen, da der Compiler das Objekt, das geworfen
wird (hier ein Zeiger) zerstört, aber nicht das Heap-Objekt: das müsste
man im catch nun explizit zerstören. Deswegen niemals heap-allocated
Objekte der Zeiger werfen. Oder SmartPtr. Aber die dyn. Allokation
produziert natürlich viel Laufzeit-Overhead.
Der Unterschied zwischen throw 20; und throw Bla; hängt davon ab, was
der Ctor von Bla macht. Da ein Objekt immer (mindestens) ein Byte
umfasst, ein int aber ggf. größer ist, könnte Bla günstiger sein.
Aber wie gesagt: erstens: exceptions sind old-school und zweitens: auf
Minimal-Systemen möchte man sie eh nicht ...
Wilhelm M. schrieb:> Sheeva P. schrieb:>> Wie groß ist eigentlich der Code Bloat, wenn man anstelle einer>> Exception-Instanz einen einfachen Integer-Fehlercode wirft (soetwas wie>> "throw 20;" statt "throw new RuntimeError("bla");")?>> Das sollte man nicht machen, da der Compiler das Objekt, das geworfen> wird (hier ein Zeiger) zerstört, aber nicht das Heap-Objekt: das müsste> man im catch nun explizit zerstören. Deswegen niemals heap-allocated> Objekte der Zeiger werfen. Oder SmartPtr. Aber die dyn. Allokation> produziert natürlich viel Laufzeit-Overhead.
Schon klar, aber hier geht es ja um den besonderen Anwendungsfalls auf
einem Mikrocontroller -- wo viele herkömmliche Empfehlungen nur
teilweise oder gar nicht gelten. Und genau deswegen meine Frage: wie
groß ist der Overhead in diesem speziellen Fall?
> Aber wie gesagt: erstens: exceptions sind old-school und zweitens: auf> Minimal-Systemen möchte man sie eh nicht ...
Exceptions sind IMHO keineswegs old-school, sondern ein ausgesprochen
wert- und sinnvolles Instrument zur sauberen Behandlung von
Fehlersituationen. Ob man sie auf Minimal-Systemen will oder nicht,
hängt allerdings nicht zuletzt davon ab, wieviel Mehraufwand an
Ressourcen sie produzieren...
Sheeva P. schrieb:> Wilhelm M. schrieb:>> Sheeva P. schrieb:>>> Wie groß ist eigentlich der Code Bloat, wenn man anstelle einer>>> Exception-Instanz einen einfachen Integer-Fehlercode wirft (soetwas wie>>> "throw 20;" statt "throw new RuntimeError("bla");")?>>>> Das sollte man nicht machen, da der Compiler das Objekt, das geworfen>> wird (hier ein Zeiger) zerstört, aber nicht das Heap-Objekt: das müsste>> man im catch nun explizit zerstören. Deswegen niemals heap-allocated>> Objekte der Zeiger werfen. Oder SmartPtr. Aber die dyn. Allokation>> produziert natürlich viel Laufzeit-Overhead.>> Schon klar, aber hier geht es ja um den besonderen Anwendungsfalls auf> einem Mikrocontroller -- wo viele herkömmliche Empfehlungen nur> teilweise oder gar nicht gelten. Und genau deswegen meine Frage: wie> groß ist der Overhead in diesem speziellen Fall?
Schaus Dir an!
Aber wie gesagt: per raw-Pointer ist extrem Fehlerträchtig, da expl.
delete notwendig.
>> Aber wie gesagt: erstens: exceptions sind old-school und zweitens: auf>> Minimal-Systemen möchte man sie eh nicht ...>> Exceptions sind IMHO keineswegs old-school, sondern ein ausgesprochen> wert- und sinnvolles Instrument zur sauberen Behandlung von> Fehlersituationen.
Wie gesagt: std::optional oder splices oder exploding-return-types
führen i.A. zu besserer Code-Struktur.
> Ob man sie auf Minimal-Systemen will oder nicht,> hängt allerdings nicht zuletzt davon ab, wieviel Mehraufwand an> Ressourcen sie produzieren...
Genau! Und da ist eine Exception auf dem Stack wesentlich
leichtgewichtiger. Am besten als integral_constant<>, dann hat man alle
Info im Typ und nicht als Wert und es reicht damit ein Byte!
Wilhelm M. schrieb:> Und da ist eine Exception auf dem Stack wesentlich leichtgewichtiger. Am> besten als integral_constant<>, dann hat man alle Info im Typ und nicht> als Wert und es reicht damit ein Byte!
Vielleicht würdest du ja mal einen Wiki-Artikel hier im Wiki mit
Codebeispielen für sowas anfangen? Das fände ich deutlich sinnvoller,
als hier im Thread, denn wer diese Dinge noch nicht in den Fingern
hatte, wird sich aus den paar Stichworten kaum einen Reim machen
können.
Vorschlag: C++ auf Mikrocontrollern
Jörg W. schrieb:> Vorschlag: C++ auf Mikrocontrollern
Ja, das würde mich auch erfreuen.
Ich habe hier gerade ein kleines Programm vor mir liegen.
C++ ist der Standard im Arduino Umfeld.
Da stellt sich die Frage "Welche Programmiersprache auf µC?" also eher
nicht.
In dem Programm werden ein paar Arduino Komfort Funktionen benutzt.
z.B. ein 2 Sekunden delay()
Und ein paar C++11 Features.
Viel tut das Programm nicht:
6 Taster abfragen, und die zugeordnete LED leuchten lassen.
Taste gedrückt, LED geht an.
Taste losgelassen, die LED geht nach 1 Sekunde aus.
Schickt man ein 'p' über die serielle Konsole, zeigt es die aktuelle Pin
Zuordnung
1
constunsignedlongwartezeit=1000;// leuchtdauer in ms
Arduino F. schrieb:> Jörg W. schrieb:>> Vorschlag: C++ auf Mikrocontrollern> Ja, das würde mich auch erfreuen.>> Ich habe hier gerade ein kleines Programm vor mir liegen.> C++ ist der Standard im Arduino Umfeld.> Da stellt sich die Frage "Welche Programmiersprache auf µC?" also eher> nicht.
Wenn Du Vergleichbarkeit mit anderen Realisierungen herstellen möchtest,
wäre es gut wenn Du die Ausgabe von
1
$avr-nm-SCn-tdxxx.elf
oder
1
$avr-sizexxx.elf
hier posten würdest und auch sagen würdest, welcher µC das ist. Denn
viele werden dieses Beispiel nicht nachvollziehen können / wollen, weil
sie (wie ich) Arduino agnostisch sind ...
Da will ich doch gerne drauf antworten
Wilhelm M. schrieb:> Wenn Du Vergleichbarkeit mit anderen Realisierungen herstellen möchtest,
Möchte ich eigentlich nicht.... war zumindest nicht Ziel
Das Beispiel soll kein RessourcenSparWunder sein.
Wilhelm M. schrieb:> sagen würdest, welcher µC
Mit "Getestet auf einem Arduino UNO" ist der ATMega328P gemeint.
Wilhelm M. schrieb:> oder> $ avr-size xxx.elf
Du meinst sowas?:
1
Der Sketch verwendet 2.930 Bytes (9%) des Programmspeicherplatzes. Das Maximum sind 32.256 Bytes.
2
Globale Variablen verwenden 240 Bytes (11%) des dynamischen Speichers, 1.808 Bytes für lokale Variablen verbleiben.
Die IDE nutzt intern avr-size.
128 Byte Ram gehen alleine für die Serial FiFos drauf
Arduino F. schrieb:> Wilhelm M. schrieb:>> Wenn Du Vergleichbarkeit mit anderen Realisierungen herstellen möchtest,> Möchte ich eigentlich nicht.... war zumindest nicht Ziel
Ok, allerdings war das ja eigentlich das Kernthema hier.
> Du meinst sowas?:
... ja
>
1
> Der Sketch verwendet 2.930 Bytes (9%) des Programmspeicherplatzes. Das
2
> Maximum sind 32.256 Bytes.
3
> Globale Variablen verwenden 240 Bytes (11%) des dynamischen Speichers,
4
> 1.808 Bytes für lokale Variablen verbleiben.
> Die IDE nutzt intern avr-size.
Das ist natürlich schon mal amtlich!
> 128 Byte Ram gehen alleine für die Serial FiFos drauf
Und wo bleiben dann die anderen 112 Bytes?
Evtl. könntest Du die Zeile
1
for(auto&&i:ledGruppe)Serial.print(i);
testweise auskommentieren und nochmal die Größenangaben nennen (nur für
den Spaß ;-))
Aber gerne doch...
Alles, was mit Serial zu tun hat, rausgeworfen.
1
Der Sketch verwendet 1.414 Bytes (4%) des Programmspeicherplatzes. Das Maximum sind 32.256 Bytes.
2
Globale Variablen verwenden 45 Bytes (2%) des dynamischen Speichers, 2.003 Bytes für lokale Variablen verbleiben.
// -----------------
Wenn es sich um weitere Optimierung dreht, müssten die andern Arduino
Komfort Dinger auch noch raus fliegen.
digitalRead() und seine Kumpanen, sind auch relativ fett. Nicht nur die
Funktionen an sich, sondern auch die Aufdröseltabellen(+Funktionen),
welche diese brauchen, um von der Pinnummer auf die IO Register zu
schließen.
Ein leerer Rumpf:
1
intmain(void)
2
{
3
for(;;);
4
return0;
5
}
Sagt:
1
Der Sketch verwendet 134 Bytes (0%) des Programmspeicherplatzes. Das Maximum sind 32.256 Bytes.
2
Globale Variablen verwenden 0 Bytes (0%) des dynamischen Speichers, 2.048 Bytes für lokale Variablen verbleiben.
Das Kompilat besteht dann quasi nur noch aus Interruptsprungleiste,
Stack Initialisierung und leerer main().
Es ist also möglich den Komfort komplett zu entfernen.
In der Regel zieht aber das Argument:
Für ungenutzten Speicher gibts kein Geld zurück.
// ------------- Nachtrag:
> Evtl. könntest Du die Zeile
Das bringt nicht viel, 40 Byte Flash Einsparung.
Arduino F. schrieb:> Es ist also möglich den Komfort komplett zu entfernen.
Wenn du auf setup() und loop() verzichtest und stattdessen dein eigenes
main() baust, hast du aber effektiv keinen Arduino mehr. Dann sind
Aussagen darüber witzlos.
S. R. schrieb:> Arduino F. schrieb:>> Es ist also möglich den Komfort komplett zu entfernen.>> Wenn du auf setup() und loop() verzichtest und stattdessen dein eigenes> main() baust, hast du aber effektiv keinen Arduino mehr. Dann sind> Aussagen darüber witzlos.
Na, na...
Es ist, und bleibt, weiterhin eine C++ Umgebung.
Dass die mitgelieferten Arduino Funktionen keine Performance Wunder
sind, ist nicht erst seit heute klar.
Die ansonsten positiven/willkommenen Eigenschaften, wie
Wiederverwendbarkeit und Plattformneutralität(im Arduino Zoo), müssen
der Performance geopfert werden, wenn es denn nötig ist.
Das ist keine so ganz unübliche Strategie. Findet man auch woanders.
Arduino F. schrieb:> Es ist, und bleibt, weiterhin eine C++ Umgebung.
Ja, aber keine Arduino-Umgebung mehr.
Wenn du in einem "Auto-vs-Fahrrad"-Vergleich deinen Golf nimmst und
Motor, Getriebe, Türen, Scheiben, ... entfernst, dann hast du am Ende
vielleicht etwas, was mit einem Fahrrad theoretisch vergleichbar ist.
Aber es ist definitiv kein Auto mehr, oder zumindest für den Vergleich
vollkommen untauglich.
Leere Firmwares sollten in C und C++ identisch sein. Interessant ist,
wie hoch der Preis für "Arduino" ist, wenn man zwei vergleichbare
Programme benutzt. Ob es sich im Einzelfall lohnt, muss jeder mit sich
selbst ausmachen - wie bei printf und Freunden auch.
Ein guter Vergleich - aus meiner Sicht - wäre dein Programm in vier
Varianten:
- "Arduino"
- "Arduino mit Abkürzungen" (also ohne digitalWrite / Serial)
- "C++" (aber mit der gleichen Struktur)
- "C" (als Vergleichsbasis)
S. R. schrieb:> - "Arduino mit Abkürzungen" (also ohne digitalWrite / Serial)
Den Punkt kannst du beruhigt streichen, denn das ist dann, laut deiner
eigenen Aussage, kein Arduino mehr.
S. R. schrieb:> Wenn du auf setup() und loop() verzichtest und stattdessen dein eigenes> main() baust, hast du aber effektiv keinen Arduino mehr.
Ich komme auf die gleiche Größe mit setup() und loop(). Die machen
das Kraut also nicht fett. ;-)
Arduino F. schrieb:> S. R. schrieb:>> - "Arduino mit Abkürzungen" (also ohne digitalWrite / Serial)> Den Punkt kannst du beruhigt streichen, denn das ist dann, laut deiner> eigenen Aussage, kein Arduino mehr.
Da wolltest du mich dann absichtlich missverstehen. Aber gut, belassen
wir's dabei, ich muss keine Haare spalten.
Jörg W. schrieb:> Ich komme auf die gleiche Größe mit setup() und loop(). Die machen> das Kraut also nicht fett. ;-)
Danke für den Test. ;-)
S. R. schrieb:> Ein guter Vergleich - aus meiner Sicht - wäre dein Programm in vier> Varianten:> - "Arduino"> - "Arduino mit Abkürzungen" (also ohne digitalWrite / Serial)> - "C++" (aber mit der gleichen Struktur)> - "C" (als Vergleichsbasis)
hier fehlt dann auch noch Assembler.
Wilhelm M. schrieb:> hier fehlt dann auch noch Assembler.
Dann wirds aber auch schon enger, mit der Arduino IDE.
Das kann sie nur als inline Assembler, oder als *.S in Libs
Wilhelm M. schrieb:> hier fehlt dann auch noch Assembler.
Assembler habe ich absichtlich weggelassen, weil das kein sinnvoller
Vergleich wird, sondern nur sinnbefreites Trollfutter.
S. R. schrieb:> Wilhelm M. schrieb:>> hier fehlt dann auch noch Assembler.>> Assembler habe ich absichtlich weggelassen, weil das kein sinnvoller> Vergleich wird, sondern nur sinnbefreites Trollfutter.
Mmh, wenn die Anforderungen an den Vergleich klar sind, sehe ich das
eigentlich nicht als "Trollfutter": man müsste mindestens noch
hinzufügen, dass wir einen ms-genauen Systemtick haben wollen und einen
UART mit jeweils x-Byte send/recv-Queue.
Und was bringt so ein Vergleich? Wenn die Aufgabe mit der gewünschten
Hard- und Software gelöst wurde und alle Anforderungen erfüllt sind ist
das Problem doch gelöst. Die LED Klasse ist wiederwendbar, im main wo es
um die reine Applikation geht muss ich mich nicht durch LowLevel
Initialisierungen kämpfen, wenn mehr LED/Taster nötig werden ist
lediglich die Tabelle zu erweitern, alles gut finde ich.
Arduino F. schrieb:> In der Regel zieht aber das Argument:> Für ungenutzten Speicher gibts kein Geld zurück.
Sehe ich mittlerweile privat genau so. Ich kenne zwar einige Schrauben
zum justieren des Speicherverbrauchs, aber eine schöne C++-HAL ist mir
die "Verschwendung" durchaus wert. Ich nehme gerne Interfaces (abstrakte
Klassen) her, was natürlich RAM verbrät für die vtables, aber diese Art
so zu coden gefällt mir richtig gut und da nehme ich das gerne in Kauf.
Wobei ich die Tage ehrlich gesagt schon arg mit mir kämpfen musste: Ich
stand ganz kurz davor aus RAM-spargründen virtuelle Methode aus einem
Interface zu verbannen, die durchaus nützlich, jedoch nicht notwendig
waren. Aber die Bequemlichkeit in der Benutzung hat gesiegt ;). Es
bleibt jetzt so wie es ist.
Mal ganz ehrlich - wann schafft man es schon wirklich mal einen
Atmega238P zu füllen? Der reicht für kleinere Steuerungsaufgaben doch
sehr oft aus und Arduino bringt den auch nicht so schnell voll. Zur Not
müsste man dann halt ein STM32-CM3 oder ähnliches hernehmen.
Wenn man natürlich auch privat den Anspruch hat immer auf alle
Ressourcen
zu achten (die Phase hatte ich sehr lange), dann wird man mit so etwas
wie Arduino nicht glücklich. ;)
Grüße Oliver
Langsam kristallisiert sich prinzipiell die folgende Tatsache heraus:
Jeder soll eben auf seine Art selig werden und wie er seine Projekte
nach besten Wissen und Können durchzieht. Ich finde, diese ganze
Diskussion bringt wenig. Ganz abgesehen davon, wer mit einer
Mikroprozessor/uC umgehen kann, sollte sehr wenig Mühe haben mit anderen
umzugehen. Wenn man mit einer Programmiersprache gut umzugehen weiß,
findet immer Lösungen seiner Probleme. Manchmal kommt mir vor man
bewertet zu viele Programmiersprachen nach ihren *exappeal;-)
Wenn man ehrlich ist, reicht für die alltäglichen
Steueraufgabenprojekte auch der etwas bescheidene 328P und wer mehr
Leistung und Platz braucht dem stehen genug Pfade zum Leistungsanstieg
zur Auswahl. Für viele Anwendung ist es mir sowieso ziemlich
gleichgültig auf welchen uC ich meine Projekte durchziehe. Solange der
notwendige Funktionsumfang gegeben ist, spielen die alle ihre Liedchen.
Das war mein Freitagsbeitrag,
Schönes Wochenende!
P.S. Zeit einen Radausflug bei dem schönen Wetter zu machen anstatt im
Labor zu verkümmern und wunderlich zu werden...
Oliver J. schrieb:> Arduino F. schrieb:>> In der Regel zieht aber das Argument:>> Für ungenutzten Speicher gibts kein Geld zurück.>> Sehe ich mittlerweile privat genau so. Ich kenne zwar einige Schrauben> zum justieren des Speicherverbrauchs, aber eine schöne C++-HAL ist mir> die "Verschwendung" durchaus wert.
Das hat weniger etwas mit privat vs. professionel zu tun. Sondern eher
etwas mit den Stückzahlen. Auch ein "Profi" wird bei kleinen Stückzahlen
versuchen, die Entwicklungszeiten zu reduzieren.
Eine "schöne" HAL würde aber auch keine Resourcen verschwenden. Darum
geht es doch die ganze Zeit in dieser Diskusion: In C++ kannst Du
Abstraktionen ohne Overhead schaffen!
> Ich nehme gerne Interfaces (abstrakte> Klassen) her, was natürlich RAM verbrät für die vtables, aber diese Art> so zu coden gefällt mir richtig gut und da nehme ich das gerne in Kauf.
Das ist Quatsch (mit Verlaub): die vtables sollten im ROM liegen, wenn
Deine tool chain nicht kaput ist. Im RAM liegt maximal ein Zeiger auf
den Anfang der Tabelle. Aber was willst Du mit Laufzeitpolymorphy, wenn
in Deiner Applikation die Konfiguration statisch ist?
> Wobei ich die Tage ehrlich gesagt schon arg mit mir kämpfen musste: Ich> stand ganz kurz davor aus RAM-spargründen virtuelle Methode aus einem> Interface zu verbannen, die durchaus nützlich, jedoch nicht notwendig> waren. Aber die Bequemlichkeit in der Benutzung hat gesiegt ;). Es> bleibt jetzt so wie es ist.
Und, hast Du Dir den das Map-File mal genauer angeguckt, bevor Du mit
Deinen Überlegungen angefangen hattest?
mfg Torsten
Torsten R. schrieb:> Das ist Quatsch (mit Verlaub): die vtables sollten im ROM liegen, wenn> Deine tool chain nicht kaput ist. Im RAM liegt maximal ein Zeiger auf> den Anfang der Tabelle.
Kein Grund unhöflich werden.
Das handhabt der avr-g++ meines Wissens so. Da kann ich nichts dafür.
Liegt wahrscheinlich an der PROGMEM-Problematik. Aber ist mir eigentlich
auch egal, denn die paar Bytes stören mich ehrlich gesagt nicht wirklich
solange ich noch genug Reserven hab.
P.S. das Teil hier schaut ganz interessant aus. Sollte ich mir
wahrscheinlich mal ansehen:
https://github.com/jcmvbkbc/avr-flash-vtblTorsten R. schrieb:> Aber was willst Du mit Laufzeitpolymorphy, wenn> in Deiner Applikation die Konfiguration statisch ist?
So erzeuge ich halt Code. Mir ist leider keine Art bekannt z.b. ein
Proxy-Pattern oder überhaupt Interfaces ohne so etwas komfortabel
umsetzen. Dir?
Torsten R. schrieb:> Das hat weniger etwas mit privat vs. professionel zu tun. Sondern eher> etwas mit den Stückzahlen. Auch ein "Profi" wird bei kleinen Stückzahlen> versuchen, die Entwicklungszeiten zu reduzieren.
Stimmt. Dachte das wäre jedem hier klar und hab mir die Passage im Post
deshalb geschenkt.
Torsten R. schrieb:> Und, hast Du Dir den das Map-File mal genauer angeguckt, bevor Du mit> Deinen Überlegungen angefangen hattest?
Ja das hab ich. Ich hab mir sogar die vtables angesehen.
Torsten R. schrieb:> Eine "schöne" HAL würde aber auch keine Resourcen verschwenden. Darum> geht es doch die ganze Zeit in dieser Diskusion: In C++ kannst Du> Abstraktionen ohne Overhead schaffen!
Das geht natürlich, aber wozu sollte ich das tun, wenn ich die
Anforderung nicht habe?
P.S. Schätze mein ironisch gewählter Nickname hat wohl ein falsches Bild
von mir vermittelt. Dafür muss ich mich dann wohl bei Dir entschuldigen.
:D
Grüße Oliver
Torsten R. schrieb:> Das ist Quatsch (mit Verlaub): die vtables sollten im ROM liegen,> wenn Deine tool chain nicht kaput ist.
G++ legt VTables nach .rodata, und .rodata wird vei avr-gcc/++ (außer
für avrtiny und avrxmega3) ins RAM lokatiert.
Grund ist, dass bei avr-g++ und
1
charlese(constchar*c)
2
{
3
return*c;
4
}
nichts über die Ablage bekannt ist, und Lesen aus dem Flash andere
Instruktionen erfordert als Lesen aus dem RAM. Weil man nicht wirklich
zur Laufzeit entscheiden will (und noch nichteinmal kann) liegt das
Zeug im RAM.
Und VTables brachen nicht nur Platz sondern sind bei Verwendung auch
langsamer.
Johann L. schrieb:> Und VTables brachen nicht nur Platz sondern sind bei Verwendung auch> langsamer.
Das stimmt natürlich. Es muss ja immer erst über die die Adresse der
Vtable und dem jeweiligen Offset die Methoden-Adresse geholt werden,
bevor man reinspringen kann. Für hochfrequente Aufrufe wäre die
Verwendung von virtuellen Methoden somit eher kontraproduktiv. Da
verzichte ich natürlich drauf.
Grüße Oliver
Arduino F. schrieb:> Aber gerne doch...>> Alles, was mit Serial zu tun hat, rausgeworfen.>
1
> Der Sketch verwendet 1.414 Bytes (4%) des Programmspeicherplatzes. Das
2
> Maximum sind 32.256 Bytes.
3
> Globale Variablen verwenden 45 Bytes (2%) des dynamischen Speichers,
4
> 2.003 Bytes für lokale Variablen verbleiben.
5
>
>> // -----------------
Vorher waren es 240 Bytes Ram, nun (ohne die UART-Instanz) 45 Bytes.
Also 195 Bytes für ein UART-Objekt mit 2x64-Byte FiFos. Also irgendwo
bleibt da ja recht viel auf der Strecke ... auch die 6 Led-Instanzen
sind 36 Bytes.
> Wenn es sich um weitere Optimierung dreht, müssten die andern Arduino> Komfort Dinger auch noch raus fliegen.
Auf keinen Fall. Es geht doch nicht darum, auf Komfort zu verzichten.
Ganz im Gegenteil: ich will allen Komfort den ich bekommen kann -
allerdings will ich dafür "nichts bezahlen". Und m.E. muss man dafür
auch nichts bezahlen. Allerdings braucht es dafür auch die Bereitschaft,
von manchem Standardvorgehen abzuweichen.
In Deinem Arduino Beispiel und generell ist es z.B. vollkommen unnötigt,
die Information über den Pin der Led oder des Tasters im RAM abzulegen.
Das ist eine statische Information, die in den Typ codiert werden
sollte. Typ-Information benötigen keinen zusätzlichen Speicher. Und ja:
ich kann mit Typen auch "rechnen" : das nennt sich dann Meta-Funktion.
Bei solchen Anwendungen erlebt TMP eine gewisse Renaissance.
> digitalRead() und seine Kumpanen, sind auch relativ fett. Nicht nur die> Funktionen an sich, sondern auch die Aufdröseltabellen(+Funktionen),> welche diese brauchen, um von der Pinnummer auf die IO Register zu> schließen.
Das kann der Compiler alles zur Compilezeit ausrechnen, dafür habe ich
ja einen Compiler! Und nicht zur Laufzeit: das kosten RAM und
CPU-Zyklen.
> Ein leerer Rumpf:>
1
>
2
>intmain(void)
3
>{
4
>for(;;);
5
>return0;
6
>}
7
>
> Sagt:>
1
> Der Sketch verwendet 134 Bytes (0%) des Programmspeicherplatzes. Das
2
> Maximum sind 32.256 Bytes.
3
> Globale Variablen verwenden 0 Bytes (0%) des dynamischen Speichers,
4
> 2.048 Bytes für lokale Variablen verbleiben.
Jo, das ist normal.
> Es ist also möglich den Komfort komplett zu entfernen.
s.o., auf keinen Fall.
> In der Regel zieht aber das Argument:> Für ungenutzten Speicher gibts kein Geld zurück.
Stimmt, meistens kostet es dann beim nächsten Update viel ... ;-)
Arduino F. schrieb:> Aber gerne doch...>> Alles, was mit Serial zu tun hat, rausgeworfen.>
1
> Der Sketch verwendet 1.414 Bytes (4%) des Programmspeicherplatzes. Das
2
> Maximum sind 32.256 Bytes.
3
> Globale Variablen verwenden 45 Bytes (2%) des dynamischen Speichers,
4
> 2.003 Bytes für lokale Variablen verbleiben.
5
>
>> // -----------------
Vorher waren es 240 Bytes Ram, nun (ohne die UART-Instanz) 45 Bytes.
Also 195 Bytes für ein UART-Objekt mit 2x64-Byte FiFos. Also irgendwo
bleibt da ja recht viel auf der Strecke ... auch die 6 Led-Instanzen
sind 36 Bytes.
> Wenn es sich um weitere Optimierung dreht, müssten die andern Arduino> Komfort Dinger auch noch raus fliegen.
Auf keinen Fall. Es geht doch nicht darum, auf Komfort zu verzichten.
Ganz im Gegenteil: ich will allen Komfort den ich bekommen kann -
allerdings will ich dafür "nichts bezahlen".
In Deinem Arduino Beispiel und generell ist es z.B. vollkommen unnötigt,
die Information über den Pin der Led oder des Tasters im RAM abzulegen.
Das ist eine statische Information, die in den Typ codiert werden
sollte. Typ-Information benötigen keinen zusätzlichen Speicher. Und ja:
ich kann mit Typen auch "rechnen" : das nennt sich dann Meta-Funktion.
Bei solchen Anwendungen erlebt TMP eine gewisse Renaissance.
> digitalRead() und seine Kumpanen, sind auch relativ fett. Nicht nur die> Funktionen an sich, sondern auch die Aufdröseltabellen(+Funktionen),> welche diese brauchen, um von der Pinnummer auf die IO Register zu> schließen.
Das kann der Compiler alles zur Compilezeit ausrechnen, dafür habe ich
ja einen Compiler! Und nicht zur Laufzeit: das kosten RAM und
CPU-Zyklen.
> Ein leerer Rumpf:>
1
>
2
>intmain(void)
3
>{
4
>for(;;);
5
>return0;
6
>}
7
>
> Sagt:>
1
> Der Sketch verwendet 134 Bytes (0%) des Programmspeicherplatzes. Das
2
> Maximum sind 32.256 Bytes.
3
> Globale Variablen verwenden 0 Bytes (0%) des dynamischen Speichers,
4
> 2.048 Bytes für lokale Variablen verbleiben.
Jo, das ist normal.
> Es ist also möglich den Komfort komplett zu entfernen.
s.o., auf keinen Fall.
> In der Regel zieht aber das Argument:> Für ungenutzten Speicher gibts kein Geld zurück.
Stimmt, meistens kostet es dann beim nächsten Update viel ... ;-)
Oliver J. schrieb:> Johann L. schrieb:>> Und VTables brachen nicht nur Platz sondern sind bei Verwendung auch>> langsamer.> Das stimmt natürlich. Es muss ja immer erst über die die Adresse der> Vtable und dem jeweiligen Offset die Methoden-Adresse geholt werden,> bevor man reinspringen kann. Für hochfrequente Aufrufe wäre die> Verwendung von virtuellen Methoden somit eher kontraproduktiv. Da> verzichte ich natürlich drauf.>> Grüße Oliver
Ich würde erst mal nachschauen, ob der GCC nicht selber gemerkt hat, daß
es sich um Scheinvirtualirät handelt. Der hat nämlich einen
"Devirtualizer". Man sollte ein virtuell formuliertes, von einem
tatsächlich virtuellen Problem unterscheiden.
So wie eine mit diversen variadic Templates gebaute GPIO Lib durchaus
ein
Carl D. schrieb:> Ich würde erst mal nachschauen, ob der GCC nicht selber gemerkt hat, daß> es sich um Scheinvirtualirät handelt. Der hat nämlich einen> "Devirtualizer". Man sollte ein virtuell formuliertes, von einem> tatsächlich virtuellen Problem unterscheiden.
Danke Dir. Das ist ein sehr guter Punkt. Den Compilerschalter
'-fdevirtualize' kannte ich bisher noch nicht. Jedoch bringt er in
meinem Fall leider keine Besserung.
Edit:
Es wurden definitiv vtables für folgende Verebungshierachien erzrugt:
Interface <- abstrakte Basisklasse <- n verschiedene Implementierungen
Interface <- ProxyKlasse
Grüße Oliver
>> werden kann.>> Falsch: werden muss!> Wenn nicht, dann hat man etwas beim Design der Template-Bibliothek> massiv(!) falsch gemacht.
Keine Frage, nur wird ja oft behauptet "niemals werden kann" und wenn
man die Arduino-Variante anschaut, dann versteht man warum.
In meinen Augen hat auch die ArduinoVariante (damit meine ich das ganze
Framework) durchaus ihre Daseinsberechtigung, gerade für die Zeilgruppe
der Platform. Ich kann mir irgendwie nicht vorstellen, das sich eine
Templateumsetzung genau so einfach handhabt. Kann mich aber natürlich
auch täuschen.
Wobei wie schon gesagt die Arduinovariante ganz klar verliert, wenn man
nichts zu verschenken hat.
Hier haben sich ein paar Leute mal zur Problematik "C++ Interface vs.
Template" ausgelassen. Finde ich ziemlich interessant:
https://stackoverflow.com/questions/16586489/c-interface-vs-template
Grüße Oliver
Oliver J. schrieb:> In meinen Augen hat auch die ArduinoVariante (damit meine ich das ganze> Framework) durchaus ihre Daseinsberechtigung, gerade für die Zeilgruppe> der Platform. Ich kann mir irgendwie nicht vorstellen, das sich eine> Templateumsetzung genau so einfach handhabt. Kann mich aber natürlich> auch täuschen.
Man könnte einiges verbessern. So ist z.B. kein dynamisches Mapping von
Pins erforderlich und es ist zur Übersetzungszeit auch kein Geheimnis,
welche Pins der Ziehlplattform abzuschaltende Zusatzfunktionen ausführen
(PWM). Da die Arduine-Variant aber in einer unübersehbaren Zahl von
Libraries verwendet wird, müßte man diese auch anpassen. Schlechtes
Base-Library-Design hat eben was virulentes.
> Hier haben sich ein paar Leute mal zur Problematik "C++ Interface vs.> Template" ausgelassen. Finde ich ziemlich interessant:> https://stackoverflow.com/questions/16586489/c-interface-vs-template
Ja, da geht es bei einer Antwort bzgl. Performance um Intel-PC's, die
mit indirekten CALL's weniger Problem wie ein 16MHz 8bitter haben.
Das Argument "Compile Time zu lang" ist auf μC Zielsystemen auch keins.
Ob der GCC für "plain C" für eins meiner Projekte 100ms braucht, oder
mit "Template-Abstraktions-Wahnsinn" 700ms, ist mir egal. Dabei sind das
700% mehr!
Und alle anderen sagen klar: Templates! Wobei es möglich ist, ein
Interface mit virtuellen Methoden und einer boardspezifischen
Implementierung zu haben. Wenn der Compiler die Source der letzteren
hat, kann er daraus leicht ein "non-virtuelles" Binary machen.
Oliver J. schrieb:> Torsten R. schrieb:>> Das ist Quatsch (mit Verlaub):> Kein Grund unhöflich werden.
Das hast Du Recht: Entschuldige bitte.
> Torsten R. schrieb:>> Aber was willst Du mit Laufzeitpolymorphy, wenn>> in Deiner Applikation die Konfiguration statisch ist?> Mir ist leider keine Art bekannt z.b. ein> Proxy-Pattern oder überhaupt Interfaces ohne so etwas *komfortabel*> umsetzen. Dir?
Wenn Du tatsächlich in Deiner Applikation sowohl mit einer lokalen, als
auch einer remote Gegenstelle kommunizieren must, dann ist das
Proxy-Pattern natürlich passend. Dann kannst Du ggf. Entscheidungen erst
zu Laufzeit treffen.
Wenn Du aber zwei Applikationen hast, und in der einen kommuniziert eine
Komponente immer mit einem lokalen Gegenstück und in der anderen
kommuniziert diese Komponente tatsächlich immer mit einem remote
Gegenstück, dann möchtest Du diese Komponente natürlich gerne in beiden
Applikationen verwenden, must aber keine Laufzeitpolymorpie verwenden,
weil bereits zur Compile-Zeit klar ist, welches Gegenstück verwendet
wird:
1
class remote
2
{
3
public:
4
void do_it();
5
private:
6
/* state */
7
...
8
};
9
10
class local
11
{
12
public:
13
void do_it();
14
};
15
16
template < class DoIt >
17
class algorithm : private DoIt
18
{
19
public:
20
void do()
21
{
22
...
23
this->do_it();
24
...
25
}
26
};
algorithm erbt von der Implementierung (das ist unüblich), erlaubt aber
empty base class optimization im Fall, dass DoIt selbst keine Daten hat
(wie im Fall local angedeutet). Wenn klar ist, dass Teile keinen State
haben werden, kann man die Funktionen auch static definieren. Das ist
nur ein Beispiel, wie man soetwas erreichen kann.
Stichworte: Compile Time Polymorphie, Duck Typing, Curiously Recurring
Template Pattern, Traits, Mixins.
> Torsten R. schrieb:>> Eine "schöne" HAL würde aber auch keine Resourcen verschwenden. Darum>> geht es doch die ganze Zeit in dieser Diskusion: In C++ kannst Du>> Abstraktionen ohne Overhead schaffen!> Das geht natürlich, aber wozu sollte ich das tun, wenn ich die> Anforderung nicht habe?
Wenn Du nicht die Anforderungen hast, dass es schön werden soll, dann
wird es natürlich nicht schön ;-)
mfg Torsten
Torsten R. schrieb:> Wenn Du tatsächlich in Deiner Applikation sowohl mit einer lokalen, als> auch einer remote Gegenstelle kommunizieren must, dann ist das> Proxy-Pattern natürlich passend. Dann kannst Du ggf. Entscheidungen erst> zu Laufzeit treffen.
Bei mir muss nix zur Laufzeit entschieden werden. Der Compiler bekommt
nur halt nicht alles auf einen Schlag. Wenn ich mal in Platznot komme,
dann werde ich sicher nen Unitybuild hernehmen. Dann sollten die vtables
ja verschwinden. ;)
Ich erläutere einfach mal was ich umgesetzt hab.
Konkret geht es bei mir um die I2C-Driver Architektur. Jedes Device
bekommt im Konstruktor eine Referenz auf einen I2C-Bus. Der Bus ist
komplett abstrakt. Das Pattern Dependency Injektion hab ich deswegen
gewählt, weil sich in meinen Augen so ganz einfach Mocks in die
TreiberKlassen reingeben lassen und so der UnitTest zum Kinderspiel
wird. Abstrakt ist das ganze, weil ich zwei verschiedene
Implementierungen von I2C hab (Hardware und Software-Bitbanging) so kann
ich je nach Lust und Laune eine der beiden Implementierungen nehmen, je
nachdem was gerade benötigt wird und die IC-Treiber brauchen kein Wissen
darüber, was sich dahinter befindet. Der Proxy den ich erwähnt hab dient
dem Zugriff auf 8 verschiedene I2C-Busse über einen I2C-Multiplexer. Da
der Proxy quasi vom Interface her ein I2C-Bus ist, kann ich ihn wieder
bequem in die IC-Treiber hineingeben.
Torsten R. schrieb:> Wenn Du nicht die Anforderungen hast, dass es schön werden soll, dann> wird es natürlich nicht schön ;-)
Gut das Schönheit im Auge des Betrachters liegt. ;)
Mir ist natürlich Bewusst, dass das was ich tu nicht wirklich für so
kleine Platformen geeignet ist, aber es funktioniert trotzdem in meinen
Projekten gut und ich habe wie schon gesagt keine Schmerzen bei der
Performance und Platz ist in der Egel auch genug da. Ein so umgesetzter
I2C-EEPROM-Programmer auf einem Atmega32U4 schafft über die USB-Serielle
von Arduino und dem HardwareI2C-Treiber einen 24C512 in 5 Sekunden
komplett zu befüllen und zu verifizieren. Damit bin ich mehr als
zufrieden. ;) Wahrscheinlich geht da noch etwas mehr, aber für mich
ausreichend.
Meines Erachtens werden bei der ganzen (und vorherigen Diskussionen in
diesem
Forum) zwei grundsätzliche Dinge nicht beachtet:
1) Die HW ist zu 99% in einer Applikation fest: damit meine ich, dass
GPIO-Register feste Adressen haben, die angeschlossenen externen
Perioheriekomponenten sind den korrespondierenden Pins fest zugeordnet,
es gibt eine definierte Anzahl von internen / externen HW-Komponenten
wie UART/I2C/SPI/RTC/USB/... mit definierten Kontrollregistern. Kurzum,
an der HW gibt es wenig bis nichts Dynamisches.
2) Die Software-Komponenten aus der Anwendungsdomäne müssen sehr wohl
dynamisch sein.
Zu 1): hier ist die Frage, wie man statische Sachverhalte modelliert.
Ein Weg wäre (wie etwa auch in den Arduino-Bibs) zunächst alles
dynamisch zu modellieren. Anschließend muss man sich dann an die
statische Natur anpassen. Wie? Da hält die OO-Küche zwar auch einige
Pattern und Idiome bereit wie etwa Singleton oder Monostate oder Facade,
aber das ist m.E. das Pferd falsch herum aufgezäumt.
Weiß ich über die statische Natur, so modelliere ich auch hier statisch,
d.h. es gibt dann eben ein MCU::Uart<0> und MCU::Uart<1>, aber die
template-Instanziierung von Uart<2> führt zu einem Compilezeitfehler.
Auch führt dieses Vorgehen nur zu ganz geringem bzw. gar keinem
Code-Bloat, wenn im MCU::Uart-Template nur die tatsächlich von der
konkreten Instanziierung abhängigen Code-Anteile drin sind. Oder bei der
verstrubbelten Timer-Modularität der AVRs sind dort dann eben nur die
spezifischen Anteile in MCU::Timer<0>. Bei den GPIOs ist das natürlich
trivial. Das führe ich weiter bis zum einzelnen Pin, also etwa
Pin<Port<B>, 3>.
zu 2) Hier brauchte ich statische oder begrenzt dynamische Container,
d.h. Container, die nur bis zu einer bestimmten Kapazität gefüllt werden
können (sofern ich keinen µC mit MMU und dyn. Seitenersetzungsverfahren
habe oder gleich ein OS habe). Hier kommt man zu 99% mit den templates
analog zu std::array<> bzw. so etwas wie stringbuffer<>, fifo<>,
fixedvector<> und stringview<> aus.
Auch im klassischen Anwendungsumfeld wie Messen/Steuern/Regeln wird man
noch ohne Laufzeitpolymorphie auskommen. Hier steht ja auch oft die
Verarbeitungsgeschwindigkeit im Vordergrund. An der Grenze zu
komplexeren UIs wird man dann um Laufzeitpolymorphie wohl nicht umhin
kommen. Hier sind dann aber die Laufzeitanforderungen moderat, so daß
man sich das dann auch leisten kann.
Im Übrigen kann man die klassischen Laufzeit-Ansätze wie etwa die
Iteration über alle Elemente eines Containers, etwa
1
for(constauto&item:container){...}
analog im statischen haben: das nennt sich klassisch in C++
template-meta-programmierung: hier hat man es mit Meta-Funktionen zu tun
(Meta-Funktionen sind selbst wieder templates, denn man "rechnet" ja
hier mit Typen und nicht mit Werten):
Mit
1
template<typename...T>
2
structList{
3
staticvoidinit(){
4
(T::init(),...);
5
}
6
};
kann man
1
usingled1=Pin<Port,B>,1>;
2
usingled2=Pin<Port,B>,4>;
3
//...
4
usingallLeds=List<led1,led2>;
5
allLeds::init();
verwenden, und damit
kann man etwa mit Faltungsausdrücken iterieren (zur Compilezeit). Was
dabei herauskommt ist eine Folge (keine Schleife, kein Funktionsaufruf)
von Initialisierungsanweisungen.
Hat man sich mit dieser TMP-Technik etwas angefreundet, kann man das
obige in der Schreibweise weiter verkürzen, indem man ein paar
allgemeine Meta-Funktionen zum Manipulieren von Typ-Listen schreibt:
Die using-Deklarationen sind gewissermaßen Meta-Funktionsaufrufe zur
Compilezeit.
Noch schöner wird das ganze, durch concepts und constraints (ab z.B.
g++-6.1 enthalten). Ein constraints ist in etwa vergleichbar mit einem
Interface zur Laufzeit: damit kann man eine implizite
Template-Typanforderung explizit machen:
1
template<typenameT>
2
conceptboolisIsrCallback(){
3
returnrequires(Tt){
4
T::isr();
5
T::interrupt_number;
6
typenameT::interrupt_type;
7
};
8
}
Auf die Art und Weise kann man dann z.B. die Template-Typ-Parameter
beschränken: man verwendet obiges concept als sog. contraint in einer
template-definition:
1
template<typenameT>
2
requiresisIsrCallback<T>()structController{};
oder kürzer
1
template<isIsrCallbackT>structController{};
Das alles habe ich nur angerissen, doch ich kann eigentlich jedem nur
empfehlen, sich mit diesen Dingen, v.a. eben varidischen templates,
fold-expressions, constexpr, IIFE, ... auseinander zu setzen. Meines
Wissens gibt es dazu bislang kein gute und umfassendes neues(!) Buch
(alte zu TMP schon, aber die benutzen meistens noch alte Paradigmen wie
SFINAE, ...).
Wilhelm M. schrieb:> Das alles habe ich nur angerissen, doch ich kann eigentlich jedem nur> empfehlen, sich mit diesen Dingen, v.a. eben varidischen templates,> fold-expressions, constexpr, IIFE, ... auseinander zu setzen. Meines> Wissens gibt es dazu bislang kein gute und umfassendes neues(!) Buch> (alte zu TMP schon, aber die benutzen meistens noch alte Paradigmen wie> SFINAE, ...).
Vielleicht solltest du ein Buch ala "modern embedded C++" schreiben. ;)
Schreibst du selbst deine ganzen Bibliotheken? Ich hab mir nämlich
vieles von dir abgeschaut und teils auch selbst nach bestem Wissen eine
"template library" ala "Uart<1>" für die STM32L4 Serie gebastelt.
Leider stößt man dabei immer wieder auf einige Steinchen.
Anstelle von "Uart<1>" verwende ich sowas wie "Uart<DeviceTree:Uart1>",
wo etwa Register Definitionen, Interrupts, DMA Kanäle, usw. drin sind.
Das funktioniert soweit ganz gut, was ich aber sehr mühsam finde ist,
wenn etwa Uart1 2x Features hat, die Uart2 nicht bietet.
Aktuell verwende ich eben SFINAE um diverse (statische) Methoden im
Template zu deaktivieren, sollte jenes Feature in der entsprechenden
Peripherie nicht vorhanden sein. Leider artet das bei komplexen Dingen
wie etwa den STM Timern in sehr mühsame Arbeit aus... Da gibts aktuell
glaub ich 5-6 verschiedene Timer Implementierungen, die alle ein klein
wenig anders sind. Wie gehst du mit sowas um?
Vincent H. schrieb:> Vielleicht solltest du ein Buch ala "modern embedded C++" schreiben. ;)
Ich schrieb doch schon: ein Artikel hier im Wiki wäre wirklich sehr
sinnvoll. Es nützt einem nicht viel, wenn jemand die ganze Zeit
nur sagt: „Ist doch ganz einfach!“ Es ist viel hilfreicher, wenn
diejenigen, die wissen, wie es ganz einfach geht, ihr Wissen auch
weitergeben - nicht nur in einem Thread, sondern an einer Stelle, wo
man es auch später wiederfindet. Das Wiki hier parallel zum Forum
ist ein guter Platz dafür.
So ein interessanter Thread! Bitte bitte liebe C++ cracks, teilt etwas
von eurem Wissen in einem Wiki Artikel, es wäre zu schade, wenn solche
Sachen hier untergingen.
Vincent H. schrieb:> Vielleicht solltest du ein Buch ala "modern embedded C++" schreiben. ;)
Ja, so etwas entsteht gerade ...
> Schreibst du selbst deine ganzen Bibliotheken?
Ja ... (das hat die unterschiedlichsten Gründe)
> Leider stößt man dabei immer wieder auf einige Steinchen.
Sonst wäre es ja nicht spannend ...
> Aktuell verwende ich eben SFINAE um diverse (statische) Methoden im> Template zu deaktivieren, sollte jenes Feature in der entsprechenden> Peripherie nicht vorhanden sein. Leider artet das bei komplexen Dingen> wie etwa den STM Timern in sehr mühsame Arbeit aus... Da gibts aktuell> glaub ich 5-6 verschiedene Timer Implementierungen, die alle ein klein> wenig anders sind. Wie gehst du mit sowas um?
Irgendeiner muss die Arbeit ja machen, wenn die HW-Struktur so ... ist.
Und in solchen Fällen eine akzeptable Lösung zu finden, ist wirklich
eine Kärrnerarbeit.
Also ich persönlich finde SFINAE zwar nützlich, aber eigentlich nur bei
freien Funktionstemplates sinnvoll, um Überladungen für bestimmten Typen
zu aktivieren. Der Klassiker ist hier wohl im µC-Bereich, dass man für
die Bits der Kontrollregister scoped enums einführt, und dann gezielt
mit einem Trait die Operatoren (etwa |) einschaltet (type safe
registers), die man zulassen möchte.
Überladungen in Klassentemplates per SFINAE ein/auszuschalten finde ich
nicht so "schön". Die Alternative, Template-Spezialisierungen zu
verwenden (full-specializations) ist auch unpraktisch, weil ich gerne
header-only Bibliotheken möchte (dann muss man sich nicht um das Bauen
der Bibliotheken kümmern), und dass dann ggf. zu ODR-Problemen führt.
Die m.E. gangbare Variante ist es, über Constraints
Klassentemplatespezialisierungen einzuführen und ggf. gemeinsame Anteile
als CRTP / mixin zu bauen. Der Vorteil hier ist, dass man ein Concept
schreiben kann, dass wie eine vollst. Spezialisierung wirkt, aber zu
keinen ODR-Problemen führt, und das es sehr kompakt formuliert werden
kann. Andernfalls hat man ja im(!) Template x-fach dieses SFINAE
Gewurschtel drin.
Dies bedeutet zugegebener Maßen, dass Du einen Compiler brauchst, der
Concepts unterstützt (wie etwa gcc ab 6.1).
Jörg W. schrieb:> Vincent H. schrieb:>> Vielleicht solltest du ein Buch ala "modern embedded C++" schreiben. ;)>> Ich schrieb doch schon: ein Artikel hier im Wiki wäre wirklich sehr> sinnvoll.
Auch das habe ich gelesen ... ;-)
> Es nützt einem nicht viel, wenn jemand die ganze Zeit> nur sagt: „Ist doch ganz einfach!“ Es ist viel hilfreicher, wenn> diejenigen, die wissen, wie es ganz einfach geht, ihr Wissen auch> weitergeben - nicht nur in einem Thread, sondern an einer Stelle, wo> man es auch später wiederfindet. Das Wiki hier parallel zum Forum> ist ein guter Platz dafür.
Da ich die Arbeit nicht mehrfach machen möchte, werde ich vermutlich ein
eigenes Dokument dort einstellen. Denn ich erzeuge alle techn. Doku mit
Asciidoctor inkl. ein paar Tools, Ausgabeformate sind dann html, pdf,
ebub, ... Originär hier im Wiki zu schreiben, ist für mich zu
umständlich.