Re: C++ in der Embedded-Programmierung: Fakten
Zunächst vielen Dank für den Artikel, Zustimmung. Ich selbst habe in 2020 5 Artikel über einen zweckmäßigen Einsatz von C++ in Embedded geschrieben, grundsätzlich passt das zueinander.
Ich möchte folgenden Hinweis als "unbedingt" loswerden. Wenn C compiliert wird, dann bitte den C++ Compiler benutzen (!). Dieser erzeugt keinen Code-overhead, macht aber bessere Prüfungen bei dusseligen Schreibfehlern, insbesondere falschen Pointer-castings. Ich verwende schon seit den 1994 C++ Compilation für C-Quellen, damals zumindestens für den PC-Test. C++ Compilation ist heute eigentlich für Embedded Controller verfügbar. Um beispielsweise dann eine Warnung nicht zu bekommen wegen C-Cast in C++, verwende ich dafür ein sehr einfaches gut lesbares Makro: C_CAST(Type*, pointer). Dies setzt für C++ um auf reinterpret_cast<Type*>(pointer). Ist C-Compilierung aktiviert, dann eben ((Type*)pointer). Die damit geschriebenen Quellen können also C und C++ Compilierung, falls die hauseigenen Richtlinien für das Zielsystem C vorschreiben. Also die Strategie wäre: Für sich selbst in C++ compilieren, Fehler besser erkennen und dann nur für das "Deployment" den C-Compiler nehmen. Weitere Details unter www.vishia.org/emc
hartmut.schorrig
@hartmut.schorrig
Best posts made by hartmut.schorrig
Latest posts made by hartmut.schorrig
-
C Programme mit C++ compiler übersetzen
-
Exception handling oder Error return?
Re: C++ Error Handling Revisited
Es gibt vereinfacht gesehen genau zwei Konzepte der Fehlerbehandlung. Entweder Error return mit einem Fehlercode oder das aus C++, Java etc. bekannte Exceptionhandling. Letzteres basiert darauf, dass unter Umgehung mehrerer Aufrufumgebungen direkt im catch-Block mit einem "Plan B" fortgesetzt wird. Das Umgehen der Aufrufumgebungen ist eine etwas schwierigere technische Lösung, muss in C++ alle Destruktoren der Aufrufumgebungen durchlaufen, also doch alle Aufrufumgebungen beachten, aber halt implizit, nicht für den Anwender sichtbar bzw. zu berücksichtigen. Also braucht es dort auch etwas länger.
Das Error return ist die alt-traditionelle Art. Sie ist dann genau richtig (!) wenn der Fehler im Aufruf sowieso zu berücksichtigen ist. Beispielsweise file-open: Es ist zu erwarten, dass das File nicht vorhanden ist. Also Rückgabewert null als klare Anzeige der Situation, einfach auszuwerten. Das ist in Java etwas zu sehr in heller Begeisterung für Exceptionhandling in die andere Richtung gedrückt, File open dort mit new FileOutputStream(...) erfordert immer das Umkleiden mit try-catch, was eigentlich doof ist. Dagegen, ein Fehler beim Schreiben des erfolgreich geöffneten Files ist nicht zu erwarten, deutet auf ein Systemproblem hin. Dort ist das try-catch das probate Mittel.
Im Artikel ist nun die Verbesserung des Error-return in den Mittelpunkt gerückt. Das Error-return ist als allgemeines Fehlerbehandlungsmittel (im Vergleich zum Exceptionhandling) aber nun wirklich alter Stil, nicht würdig des vielen Aufwands. Möglicherweise haben die funktionalen Programmiersprachen bei Exceptions ein Problem, so habe ich es herausgelesen (nicht mein Spezialgebiet), sie sind also auf das Error return angewiesen und müssen dort also etwas anbieten. Dass das in C++17 nachgeahmt wird mit dem std::variant empfinde ich als Förderung des Rückschrittes. Warum:
Wenn man davon ausgeht, dass Error-return nur dann zweckmäßig ist, wenn der Fehler erwartbar ist, dann muss die Fehlerinfo im normalen Rückgabetyp-Wertespektrum liegen. Dazu braucht es keine Variante. Um weitere Detailinfos für den Fehler erkunden zu können, gibt es üblicherweise Fehlerabfrage-Operationen, die weitere threadlokale (!!) Fehlerinformationen enthalten, nur bei Bedarf aufzurufen. errno() ist die einfachste derer. Das ist stimmig. Sinnvoll ist es auch, bei einem file-open constructor (siehe java new FileOutputStream(...)) eben immer eine Instanz zurückzugeben, die abfragbar die Fehlersituation enthält und beim nachlässigen write() dann eine Exception erzeugt. Das ist alles gut dokumentierbar und logisch verständlich.
Was ist der Vorteil des try-catch: Es geht dabei um nicht erwartbare Fehler (beispielsweise Sensor kaputt) und einem daraufhin ablaufenden Ersatzalgorithmus. Das ist ebenfalls sehr logisch erklärbar, programmierbar und wartbar. Die Zwischenebenen müssen sich nicht mit dem Weiterreichen aller eigentlich nicht vorgesehenen Fehler herumschlagen. Das ist ein super Konzept.
Nur ist dieses Konzept in C++ im Embedded Bereich offensichtlich nicht so gern verwendet. Es gibt, im Vergleich zu Java ein Grundkonzept-Problem: Destruktoren müssen durchlaufen werden. Das macht's für die Implementierung komplexer (=>Rechenzeit) und auch die Durchschaubarkeit ist schwieriger (es ist im Programm nicht ganz offensichlich, was durchlaufen wird). Java hat es da einfacher gemacht: Es gibt keine Destruktoren. Stattdessen ein explizit sichtbarer "finaly" Block der das Gleiche leistet.
Man kann aber in C++ auch das besssere Finally-Konzept anwenden, wenn man auf Destruktor-Inhalte verzichtet. Im embedded Bereich verwendet man oft sowieso nicht die Standard-Libraries, die explizit auf Destruktoren aufbaut.
Man findet auf meiner Seite www.vishia.org/emc möglicherweise interessante Lösungsansätze. -
RE: Agile Methoden machen Projektleitung überflüssig
Dem Artikel kann ich voll zustimmen, nach ein paar Jahrzehnten in Entwicklungsabteilungen. Meines Erachtens gibt es eine Rolle die hervorragt: Der Product Owner. Er kann und soll bestimmen was gemacht werden soll. Denn er kennt Notwendigkeiten, Meinungen und Testzeitpläne des Nutzers (oder ist der Nutzer selbst). Der Product Owner wird nicht bestimmen wollen, WIE etwas gemacht wird. Er wird sich auch flexibel zeigen, wenn das Team sagt "können wir nicht so".
Einen zwischengeschalteten Chef, der bestimmt "Dies bis morgen erledigen, und Sie bitte das", dabei Metainformationen zurückhält, das ist Altes Denken.Eine weitere Anmerkung. Es gibt Menschentypen. Ich möchte fokusieren auf den zielorientierten Choleriker, den kritisierenden Melancholiker, den für alles bereite Phlegmatiker, aber man muss es ihm sagen, und den etwas in die Ferne blickenden Sanguiniker, auch Feuertyp, Wassertyp, Erdetyp und Lufttyp genannt. - sollte bekannt sein. Nebenbemerkung: In den meisten Menschen schlummern mindestens zwei Typen, es kommt auch auf die Situation darauf an. Nun kenne ich das so, dass der Chef meist Feuertyp ist (sonst hätte er es nicht zum Chef geschafft) und den Typus seiner Mitarbeiter in Unkenntnis ignoriert. Im agilen Team muss es sich finden, durch soziale Kommunkation. Wenn eine Person konsequente Vorschläge macht und die Ärmel hochkrempelt um loszulegen, die Vorschläge sind aber nicht ausgegoren (der Feuertyp), dann sollte er sich mit dem Wassertypen (der alles weiß aber von selbst nicht aus dem Knick kommt) beraten. Dann wird was draus. Jeder sollte den eigenen Typus und den der Kollegen in etwa kennen, sich dessen bewusst sein. Die Typen arbeiten dann super zusammen.
-
RE: C++ in der Embedded-Entwicklung: Exceptions und Assertions
Endlich einmal ein Feedback zu einem Artikel, mit konkreten Hinweisen aus anderer Sicht. Insbesondere die 5 Varianten der Fehlerbehandlung, die der Kritiker nennt, finden meine Zustimmung.
Aber: Insbesondere Punkt 1, 4 und 5 dürfen keine dauerhafte Programmiererentscheidung sein, sondern sollten ohne Quellenänderung je nach Aufrufumgebung einstellbar sein. Beim Test (von embedded Software am PC, insbesondere Unit-Test) Verletzungen mit Exception melden, damit die Fehler überhaupt erst deutlich erkannt werden. Danach die gleiche Software im Zielsystem unter kritischen Echtzeitbedingungen nach 1. behandeln, oder nach 4. wenn ein Fehler nicht ausgeschlossen werden kann. Genau dafür habe ich meine Makros für TRY-CATCH entworfen.
Punkt 3 der Kritik, "Mythos Exception Handling kostet Rechenzeit" ist leider kein Mythos sondern gemessener Fakt und auch an sich ganz logisch und folgerichtig, wie ich im Artikel nicht ausführlich genug dargestellt habe. Modernes Exceptionhandling in C++ hat den Zusatzaufwand im Nichtfehlerfall auf fast 0 reduziert. Durchläuft ein Algorithmus ein try-catch ohne Exception, dauert er kaum länger als ohne diesen try-catch-Rahmen. Damit ist Performance gesichert und der obige Mythos tatsächlich ein Mythos, nicht wahr, diesbezüglich. Aber: Dafür dauert das throw um so länger. Und dies geht nicht, wenn eine hochzyklische kritische Echtzeitverarbeitung erfolgt. Damit ist Exceptionhandling mit C++ throw nicht anwendbar in zyklischen Interrupts oder Threads von Regelungen. Aber Exceptionhandling ist wünschenswert. Was macht eine weggelaufene Regelung wegen eines defekten Sensors? Am besten throw, und eine Ersatzhandlung im catch sichert die Funktion. Sind alle Fehlermöglichkeiten einer komplexen Software vorher per Test vorauszusagen? Nein. Man möge nicht wieder unterstellen, ich würde Unit-Test ablehnen. Auch Boeing hat bei 737 MAX sicherlich Unit-Tests durchgeführt.
Da man im embedded Bereich die Destructoren eigentlich nicht wirklich braucht bzw. bewusst darauf verzichten kann, ist der longjmp die probate Lösung. Denn er hält die Zeitbedingungen ein und funktioniert genauso. Ich möchte dies nochmals deutlich unterstreichen, in Kenntnis der Argumente von Kritikern.
Als letztes eine Bemerkung zu C++ und Java. In C++20 mit "gcc -c -x c++ -std=c++20", aufgerufen unter Cygwin, gcc (GCC) 10.2.0 kann man immer noch fehlerfrei programmieren:
ExmplClass* myClass = new ExmplClass();
(myClass+1)->set(456);Die unmittelbar hintereinanderstehenden Statements offenbaren mit dem Hinsehen oder einem Checktool selbstverständlich den Blödsinn dieser Anweisungsfolge. Stehen die Anweisungen jedoch nicht hintereinander und ist der Fehler Folge von Änderungen nicht bis ins letzte durchdacht, und Fehler beim Unittest nicht aufgefallen, dann erzeugen solche Fehler über Seiteneffekte irgendwo anders die Nullpointer oder zerstörte vtbl-Zeiger. Die Ursache sind die in den 1970-ger Jahren gefundenen einfachen Grundlagen in C, die in C++ immer noch möglich sind und verwendet werden. Man hat unter Kenntnis dieser Dinge mit Java in den 1990-gern einen Schnitt gemacht und so etwas gar nicht zugelassen (Pointerarithmetik). Daher ist Java und weitere beliebte Programmiersprachen im PC Bereich sicher und dringend zu empfehlen gegenüber C++. C und besser C++ ist notwendig (wenn man so will ein notwendiges Übel) im embedded Bereich, um direkt und optimal auf die Maschinencodeebene zu kommen. Andere Programmiersprachen haben sich dafür leider nicht etabliert. Es gibt auch für Java für Embedded passende Virtual-Machine-Lösungen, leider nur nicht so bekannt. Diese wären dann für den Teil der Datenverarbeitung (ohne Hardwarezugriffe) die bessere Lösung.
Hartmut Schorrig
-
Woher bekomme ich Ada für einen beliebigen embedded Prozessor
Re: SPARK und MISRA-C – Die Vorteile von Sprach-Subsets
Wie ich den Artikel verstanden habe: Man soll bitte in Ada programmieren, dann ist es sicher. SPARK ist eine Untermenge - sicheres Ada. MISRA-C ist eine Richtlinie für C.
Nun ist Ada zwar interessant, sicherlich gut, aber mir ist es in meiner langen Praxis noch nie richtig begegnet, nur immer vom "Hörensagen". Anders Java, vollkommen und täglich präsent.
Vielleicht ist es auch eben problematisch, eine ganz andere Programmiersprache zu erlernen, neben der C (++) Programmierpraxis. Dazu wünschte ich mir mehr Erläuterungen -
Wichtiges Thema, mehr und genauere Aussagen/Diskussionen nötig
Re: [Moderne Compiler-Optimierungen – Tricks für schlanken](schnellen Code)
Ich halte das Thema für zeitlos wichtig in der embedded Entwicklung. Es bedarf mehr und konkreterer Diskussion, was natürlich nicht in diesen Übersichtsartikel passt.
- Der Sw-Entwickler weiß welche Code-Teile schnell abgearbeitet werden müssen und welche egal sind: Hochlauf: weitgehend egal, schneller Abtastzyklus, im Interrupt, z.Bsp. 50 µs: Ganz wichtig. Da braucht es kein Analysetool. Man muss unterscheiden, allgemeine Forderung "so schnell wie möglich" etwa für Response im Serverbereich, oder "real time", physikalisch bedingte Zeiten.
- Wie ist die Empfehlung: Compiler optimieren lassen vs. selbst Hand anlegen: Ist im Artikel gut angesprochen, z.Bsp. "bitte nicht selbst mit inline-Assembler verschlimmbessern" - ich drücke es mal so krass aus. Man muss wissen wie die Compileroptimierung arbeitet. Das braucht mehr Raum zur Darstellung, denn ich denke, so ad hoc weiß das nicht jeder. Man kann durch ungeschicktes Codieren auf C/++-Ebene es dem Compiler leicht oder schwerer machen zu optimieren.
- Z.Bsp. Bedeutung von volatile gehört nicht in den Nebensatz, sondern dessen richtige Anwendung ist essentiell, wird aber im Konkretfall oft vergessen.
- Der Compiler berechnet reine Konstanten immer zur Compilezeit. Ich behaupte, das kann jeder Compiler, ich hoffe dass das stimmt. Aber wie ist es bei Nutzung von const - Definitionen im Speicher, werden die mit einbezogen? Man kann im Konkretfall in das Listing schauen (Maschinencodeview).
- Doppelt notierte Ausdrücke könnte ein Compiler automatisch vereinfachen, einmal berechnen und zwischenspeichern (wenn nicht eine Quelle volatile ist). Aber eigentlich ist es besser dies manuell schon so vorzugeben, dient ggf. auch der Übersicht. Ich denke dies gilt bei allen Compilern =>Klar benennen.
- Das Pointerbeispiel gefällt mir (ptr = arr + i; ) Aber genau dieses verschlechtert die Lesbarkeit des Sourcecode, da es keinen sachlichen Grund für den Zwischenpointer gibt. Hier rechne ich mit der Fähigkeit der Compilier, dies selbst zu optimieren. Denn: Die Sachlage ist klar, zwei Adressierungen in relativer Nähe, der Compiler kann ein Register mit der ersten berechneten Adresse füllen und die zweite per Indexadressierung in einem schnellen Maschinenbefehl ausführen.
- Beispielsweise TMS320F28xx Prozessoren können einen Indexzugriff mit Offset 0..7 in einem kurzen Maschinenbefehl. Bedeutet: Oft verwendete Variable in einer struct vorn notieren. Vergleich, gilt das ähnlich bei anderen Prozessoren? Ist das eine allgemeine Regel, (die jedenfalls nicht schadet). Das ist eine von mehreren offenen Detailfragen.
Diskussionsforen oder weiterführende Artikel? Beides hat Vor- und Nachteile, aber ich möchte beides anregen.
Hartmut Schorrig -
RE: MSVC, GCC und Clang/LLVM: Compiler-spezifische Vor- und Nachteile
Für mich war der Artikel Neuwert! Ich bin bestens vertraut mit MSVC und GCC (MinGW auf Windows), bin irgendwo über Clang gestolpert ("was ist das"), LLVM war mir bisher kein Begriff. Ist aber absolut wichtig. Mehr zu LLVM steht in https://de.wikipedia.org/wiki/LLVM Ich habe mich früher schon gefragt, ob es einen zweckdienliche Verbindung der Java-Technologie mit Bytecode und JIT und C/++ geben sollte: Mit LLVM ist diese da.
Frage: Bei klassichem Compilieren/Linken gibt es das Problem: Lange ungenutzte Routine in einem Objfile, der Objfile wird aber wegen einer anderen Routine vom Linker eingezogen, Speicherverschwendung, tut weh auf embedded small footprint Plattformen (unrelevant für PC-Programme). Die LLVM-Technologie könnte aber in der Lage sein zu erkennen was tatsächlich gebraucht wird auch unterhalb des Schnitts in "Übersetzungseinheiten". Das bedeutet für die Quelltextgestaltung (in C/++) dann man nicht mehr darauf achten braucht, bestimmte Dinge eben nicht in ein C-File zu schreiben. Es wäre dann vollkommen egal. Auch könnte die Entscheidung wann inline vollkommen der Compiler-Optimierung (nach Laufzeit oder Codegröße, ggf. auch eine max. Codegröße als Limit angebbar) überlassen werden. Hinweis: inlines sind laufzeitoptimal aber ggf. speicherintensiver, aber solange der kleine Embedded Speicher reicht, sind inlines besser.
Weitere Frage: Wie sieht es mit der automatischen Parallelisierbarkeit aus (Multicore-Aufteilung). -
RE: Testgetriebene Entwicklungsmethode für komplexe Algorithmen
Die Überschrift ist leider nicht prägnant denn das eigentliche Thema ist ja das "PIL" Prozessor in the Loop.
Aber meine Anmerkung beziehen sich auf den Begriff HIL - Hardware in the Loop. Der Begriff ist leider auch nicht prägnant, die Frage ist "welche Hardware". In der Domäne des Autors ist die äußere Umgebung die Hardware, bestehend aus den Sensoren usw. im Fahrzeug. Ich kenne den Begriff aus dem Anlagenbau, und da ist es die Steuerungshardware, die bei HIL gemeint ist, also genau das, was der Autor als "PIL" bezeichnet. Bei Automatisierungsgeräten ist das natürlich nicht nur der Prozessor, sondern halt die gesamten Schaltschränke. Ich habe daher eine Weile gebraucht und musste erst den Abschnitt genau lesen, um zu verstehen, was das HIL-Setup sein soll. Etwas, was es so im Anlagenbau nicht gibt.
Nun kann es sein, dass Sprachgebrauch selbst in benachbarten Gebieten verschieden ist, was aber bei Wissenschaftssprache nicht sein sollte. Also muss man Begriffe schärfen. Daher gefällt mir der Begriff PIL durchaus, den "Prozessor" ist konkreter als "Hardware" - wenn den nun der Begriff PIL für die Tatsache "Schaltschränke in the Loop" auch akzeptierbar wäre. Ich will dies mal als Diskussion in den Raum stellen, denn immerhin bestehen Schaltschränke aus Boards, die innen immer Prozessoren haben.
Der Ansatz, den Prozessor verlangsamt laufen zu lassen weil die äußere Umgebung per Software langsam simuliert wird (für die Fahrzeuganwendung also die Umgebungsnachbildungen, was die Sensoren erfassen), ist interessant und wichtig. Ich kenne es so, dass der Prozessor oder Schaltschrank in the Loop in Echtzeit läuft und das deshalb Aufwändungen getrieben werden, dass die Umgebung, die man nicht real haben kann (Anlage ist groß und mechanisch), möglichst in Realtime simuliert wird. Mit schnellen Prozessoren, Abtastzeiten bis 3 µs oder FPGA usw. (bin gedanklich bei Stromrichterhardware). Der Ansatz ist aber interessant. Es kommt darauf an, die Algorithmen im Target zu testen, man hat aber noch nicht die Environment-Realtime. Daher muss der Prozessor meist warten. Auch hat der Prozessor eben noch nicht alle Peripheriebaugruppen angeschlossen und wird daher über den Debugger versorgt. Das ist lesens- und denkenswert.
Ich wünsche mir mehr Anmerkungen zu Artikeln, denn auch wir Leser müssen miteinander reden und diskutieren. -
RE: Risiken bei Open-Source-Software: Warum eine Bill-of-Materials sinnvoll ist
Kein Widerspruch zum vorigen Beitrag .... ermitteln den Eindruck das der Einsatz von OSS ein Problem darstellt. vom 23.05.2018. Aber:
Ich denke der Artikel weist auf ein anderes Problem hin. Es geht nicht um Open vs. Closed sources sondern um zu viele und unkontrollierte (!) Abhängigkeiten. Da diese häufig auf Open-Source-Komponenten zutrifft (weil diese offen in Archiven bereitstehen), gibt es nur den Anschein, es ginge gegen Open Source. Das Theme ist allgemein, auch gegen Closed-Source.
Ich habe mich in letzter Zeit intensiver mit gradle build & test beschäftigt. Integrierter Test ist sehr gut, auch wenns arbeitsintensiv ist. Aber: Der umfangreiche Test kann vortäuschen, es wäre alles getestet. Dabei geht der Test ggf. nur in bestimmte vom Tester bedachte Richtungen. Eine Software ist dann gut wenn sie getestet ist und in sich harmonisch ist, beides sind notwendigen Bedingungen. Auch wichtig, trifft aber nicht diesen Kern der Aussage.
Die andere Sache an gradle und maven = Wissen der Welt ist, Abhängigkeiten werden locker und flockig aufgelöst. Etwas Abhängiges hängt wieder von etwas anderem ab. Alle abhängigen Module landen in user/.../gradle/caches/... und die Welt scheint in Ordnung.
Dieses einfache Umgehen mit Abhängigkeiten führt zu Sorglosigkeit. Letzteres ist nicht nur die Einbruchspforte für Diebe (wenn ich meine Wohnungstür sorglos offen stehen lasse), sondern macht die Software bei Problemen auch unüberschaubar und komplex. Ich denke, das ist das Thema. Ich habe mir vorgenommen dazu einen längeren Artikel zu schreiben, kommt noch. Das Thema ist aktuell.
Dr. Hartmut Schorrig -
RE: Tipps und Tricks für die Laufzeitoptimierung
Danke für den Artikel, solche Konkretthemen braucht es. Ich habe den Artikel jetzt erst nur überflogen, gespeichert aber sofort einem Kollegen weitergeleitet.