Ein Angebot von

Neun praktische Tipps für die Entwicklung von Embedded-Software

| Autor / Redakteur: Colin Walls * / Sebastian Gerstl

Optimierung von C-Code, Zugriff auf Hardware-Ports in C/C++ oder Gelichheitsprüfung von Fließkommazahlen: Hier sind wieder einige Tipps und Denkanstöße aus der Embedded-Software-Entwicklungspraxis.
Optimierung von C-Code, Zugriff auf Hardware-Ports in C/C++ oder Gelichheitsprüfung von Fließkommazahlen: Hier sind wieder einige Tipps und Denkanstöße aus der Embedded-Software-Entwicklungspraxis. (Bild: gemeinfrei / CC0)

Von Zeit zu Zeit präsentiert Colin Walls von Mentor einige nützliche Programmiertipps für Embedded-Entwickler. Dabei geht es nicht um harte und schnelle Regeln, sondern um Denkanreize und einige bewährte Vorgehensweisen aus der Entwicklungspraxis.

Dies ist eine weiterer Beitrag aus meiner gelegentlichen Serie mit nützlichen Tipps für Entwickler von Embedded-Software. In ihm beschäftige ich mich unter anderem mit der Optimierung von C-Code, dem Zugriff auf Hardware-Ports in C/C++, der Gleichheitsprüfung von Fließkommazahlen, Schreiben von lesbarem Code, Erlernen einer Sprache, Debugging etc.

Ich bin auch offen für Diskussionen, denn ein alternativer Standpunkt ist immer interessant. Kontaktieren Sie mich per Kommentar, via E-Mail oder über Social-Media.

Verwenden Sie immer ++i statt i++, wenn Sie das Ergebnis des Ausdrucks nicht sichern

Jeder C-Programmierer kennt den Unterschied zwischen der Präfix- und Postfixform des

++-Operators: ++i erhöht i und gibt den neuen Wert zurück; i++ erhöht i und gibt den alten Wert zurück. Beide sind in verschiedenen Kontexten nützlich. Wenn Sie jedoch den zurückgegebenen Wert nicht verwenden – d.h., wenn Sie den Ausdruck einfach in eine Anweisung konvertieren – spielt die Form dann überhaupt eine Rolle? Sie könnten zum Beispiel schreiben:

for (i=0; i<10; i++)
   ...
or
for (i=0; i<10; ++i)
   ...

Beide machen genau das Gleiche, aber ist eine besser als die andere?

Die Antwort ist: Meistens spielt es keine Rolle, welche Form Sie verwenden. Wenn i ein int ist, wäre der resultierende Code identisch. Wäre i jedoch eine Instanziierung einer benutzerdefinierten Klasse und der/die ++-Operator(en) überladen, dann wäre das eine andere Geschichte. Die Postfix-Form würde wahrscheinlich etwas mehr Speicherplatz belegen, um den alten Wert aufzunehmen, der bereit zur Rückgabe ist. Obwohl die Compileroptimierung den Tag retten könnte, ist die standardmäßige Verwendung des Präfixformulars gängige Praxis.

Niemals Fließkommazahlen auf Gleichheit prüfen

Bei Gleitkomma-Berechnungen besteht immer die Möglichkeit kleiner Rundungsfehler. Das Ergebnis ist Code wie:

float x, y;
...
if (x == y)
   ...

Dieser ist jedoch irreführend. Als Ergebnis einiger Berechnungen können zum Beispiel x und y beide 6,0 sein. Ihre Istwerte können jedoch 6,0000001 bzw. 6,0000002 betragen. Der Gleichheitsvergleich würde also ein falsches Ergebnis liefern. Offensichtlich würde ein Vergleich von „weniger als“ oder „mehr als“ mehr Sorgfalt erfordern.

Eine Funktion mit einem einzigen Rückkehrpunkt sorgt für klaren Code

Dieser Tipp ist etwas umstritten und kann, um ehrlich zu sein, in beide Richtungen ausgelegt werden. Die Idee ist, dass es nur einen Einstiegspunkt für eine Funktion gibt: den oberen. Es sollte daher einen einzigen Ausgangs-/Rückkehrpunkt geben: den unteren. Dies sorgt für eine klare Struktur und vermeidet die Möglichkeit, dass der Exit-Code einer Funktion übersehen wird.

Das Gegenargument lautet, dass zum Erreichen dieser Struktur in einigen Fällen eine ziemlich gewundene Logik erforderlich sein kann, um die Ausführung nach unten zu lenken. Der beste Ansatz besteht darin, eine Verifizierung der Parameter etc. durchzuführen, die zu einem frühen Exit am oberen Ende führen und dort gegebenenfalls eine oder mehrere Return-Anweisungen enthalten kann. Dann folgt der Hauptteil der Funktion, der einen einzigen Ausgang am unteren Ende hat.

Kodierungsstandards wie MISRA C fördern den Single-Exit-Ansatz und werden von einigen Sicherheitsnormen wie IEC 61508 und ISO 26262 gefordert.

Initialisieren Sie eine Variable immer zum Zeitpunkt der Definition und gehen Sie niemals davon aus, dass sie Null ist

Auch wenn der Sprachstandard besagt, dass statische Variablen in C/C++ immer mit Null initialisiert werden, ist es schlechte Programmierpraxis anzunehmen, dass dies der Fall ist. Die Lesbarkeit des Codes wird durch eine explizite Initialisierung verbessert. Diese erfolgt am besten während der Deklaration einer Variable. Man könnte aber auch argumentieren, dass Deklaration und Initialisierung [für automatische Variablen sowieso] verschiedene Dinge sind und das sollte veranschaulicht werden. Betrachten wir diesen Code:

void fun()
{
   int alpha, beta=2;
   alpha = 3;
   ...

Welche Variable wurde am Besten/Klarsten behandelt? Der generierte Code ist in beiden Fällen gleich.

Wenn Sie nur einen einzigen Aufruf machen wollen, dann sollte eine C-Funktion immer statisch sein, denn dies unterstützt die Compiler-Optimierung

Wenn Sie eine C-Funktion schreiben, die nur von einer Stelle aufgerufen werden soll und dieser Aufruf im gleichen Modul [file] erfolgt, dann sollte sie statisch sein. Dies teilt dem Compiler mit, dass es keine weiteren Aufrufe von außerhalb des Moduls gibt und keine externe Verknüpfung vorgenommen werden muss. Es eröffnet auch die Möglichkeit, eine Funktion automatisch einzubinden.

Um auf einen bestimmten Speicherbereich als Variable zuzugreifen, nutze einen Zeiger und Dereferenzierung

Allgemein wird angenommen, dass alle Embedded-Software-Entwickler das „Low-Level-Zeug“ beherrschen, zum Beispiel die Programmierung von Geräteregistern usw. Dies ist oft nicht mehr der Fall. Viele Entwickler arbeiten auf einer höheren Ebene und haben sich auf die Anwendung spezialisiert, ohne sich die zugrunde liegende Hardware bewusst zu machen. Diejenigen, die sich dem „Metall“ nähern, müssen jedoch wissen, wie sie von C aus auf Speicherplätze [device registers] zugreifen können. Das ist ganz einfach, auch wenn die Syntax etwas obskur ist. Wandeln Sie einfach eine Konstante [the address] in einen volatilen vorzeichenlosen Zeiger [say] und dereferenzieren Sie, also:

#define DEVICE (*((volatile unsigned *)0x8000000))

Sie können „DEVICE“ dann so verwenden, als wäre es eine vorzeichenlose Variable. Allerdings ist Vorsicht geboten, da sich die Geräteregister nicht immer wie normale Variablen verhalten. Beispielsweise kann ein Gerät erlauben, dass einige Daten geschrieben, aber nicht noch einmal zurücklesen werden. Das kann natürlich berücksichtigt werden, ist aber eine größere Sache.

Lange C-Funktionen sind schwer zu lesen. Deshalb sollten sie immer in bildschirmfüllende Blöcke zerlegt werden.

Vor Jahren war es gängige Praxis, dass ein Teil eines Codes [ein Unterprogramm, eine Funktion etc.] immer auf eine einzige Seite eines Zeilendruckerpapiers passen sollte [die, wenn ich mich richtig erinnere, 66 Zeilen hatte]. Dies wurde als die Menge an Code angesehen, die man problemlos für eine Überprüfung verwenden konnte. Heutzutage würde die Darstellung auf einem Bildschirm wahrscheinlich die gleiche Funktion erfüllen. Natürlich gibt es noch viele andere Dinge, mit denen man die Lesbarkeit von Code verbessern kann.

Das Erlernen von C bringt Sie auf Kurs, um C++, Java, Python, Lua zu lernen...

Die derzeit am weitesten verbreitete Sprache für die Entwicklung von Embedded-Software ist C. Obwohl C++ seit langem als Ersatz gefördert wird, steigt der Marktanteil von C sogar an. Wenn Sie also mit der Embedded-Programmierung beginnen, sollten Sie sich mit der C-Programmierung vertraut machen. Auch wenn Sie nie viel in der Sprache arbeiten werden, haben Sie den ersten Schritt gemacht, um einige der anderen weit verbreiteten Sprachen zu erlernen: C++, Java, Python, Lua.....

Ihr Gehirn ist das erste Debugging-Tool, das Sie verwenden sollten

Es ist sehr einfach, Code zu erstellen, den Compiler die Syntaxfehler aufspüren zu lassen und ihn dann an einen Debugger weiterzugeben, um ihn zum Laufen zu bringen. Es gibt einen besseren Weg:

  • schreibe den Code
  • lies den Code sorgfältig durch, um Syntaxfehler zu finden
  • geh durch den Code, um der Logik zu folgen; wenn es schwer ist, ihr zu folgen, dann schreib den Code neu
  • behebe alle Fehler [es sind Fehler – vermeide den Euphemismus „bug“]
  • erstelle den Code [und behebe dann alle Syntaxfehler, die übersehen wurden]
  • verwende den Debugger, um die Funktion des Codes zu überprüfen

Der Autor

Colin Walls, Embedded-Software-Technologist bei Mentor, a Siemens business.
Colin Walls, Embedded-Software-Technologist bei Mentor, a Siemens business. (Bild: Caroline Mann)

* Colin Walls verfügt über fast 40 Jahre Erfahrung in der Elektronikindustrie, hauptsächlich im Bereich Embedded Software. Er ist Embedded-Software-Technologist bei Mentor, a Siemens business, mit Sitz in Großbritannien. Walls hält regelmäßig Vorträge auf Konferenzen und Seminaren. Zudem ist er Autor zahlreicher Fachartikel sowie zweier Bücher über Embedded Software und betreibt ein Blog auf http://blogs.mentor.com/colinwalls.

Fünf praxisnahe C/C++-Tipps für Embedded-Software-Programmierer

Fünf praxisnahe C/C++-Tipps für Embedded-Software-Programmierer

07.01.19 - Es ist Zeit für ein paar weitere, hoffentlich hilfreiche Tipps für Embedded-Software-Entwickler. Diese Tipps sind in vielen Fällen nur gesunder Menschenverstand, aber ich denke, wir müssen sie uns immer mal wieder in Erinnerung rufen. lesen

Software-Tipp: C++ mit einem Echtzeitbetriebssystem verwenden

Software-Tipp: C++ mit einem Echtzeitbetriebssystem verwenden

13.03.18 - Bei Einsatz eines Echtzeitbetriebssystens in Embedded-Anwendungen kann die hohe Anzahl komplexer APIs, die zum Abrufen vieler RTOS-Funktionen nötig sind, unerfahrene Entwickler unter Einsatz von C abschrecken. Die Eigenschaften von C++ lassen sich hier aber gut nutzen, um auf einfache Weise zu sauberen und lesbaren Code zu kommen. lesen

Kommentar zu diesem Artikel abgeben

Schreiben Sie uns hier Ihre Meinung ...
(nicht registrierter User)

Zur Wahrung unserer Interessen speichern wir zusätzlich zu den o.g. Informationen die IP-Adresse. Dies dient ausschließlich dem Zweck, dass Sie als Urheber des Kommentars identifiziert werden können. Rechtliche Grundlage ist die Wahrung berechtigter Interessen gem. Art 6 Abs 1 lit. f) DSGVO.
Kommentar abschicken
copyright

Dieser Beitrag ist urheberrechtlich geschützt. Sie wollen ihn für Ihre Zwecke verwenden? Kontaktieren Sie uns über: support.vogel.de/ (ID: 45698353 / Implementierung)