2008-08-10 10 views
17

Ich habe an einer Reihe von verschiedenen eingebetteten Systemen gearbeitet. Sie haben alle typedef s (oder #defines) für Typen wie UINT32 verwendet.Wann sollte ich Typ Abstraktion in eingebetteten Systemen verwenden

Dies ist eine gute Technik ist, wie es zu Hause die Größe des Typs für den Programmierer treibt und macht Sie bewusst die Chancen für Überlauf usw.

Aber auf manchen Systemen wissen Sie, dass der Compiler und Prozessor wird nicht Änderung für das Leben des Projekts.

Was also sollte Ihre Entscheidung beeinflussen, projektspezifische Typen zu erstellen und durchzusetzen?

EDIT Ich denke, ich habe es geschafft, den Kern meiner Frage zu verlieren, und vielleicht sind es wirklich zwei.

Mit Embedded-Programmierung benötigen Sie möglicherweise bestimmte Größen für Schnittstellen und auch mit eingeschränkten Ressourcen wie RAM umzugehen. Dies kann nicht vermieden werden, aber Sie können wählen, die grundlegenden Typen aus dem Compiler zu verwenden.

Für alles andere haben die Typen weniger Bedeutung.
Sie müssen darauf achten, keinen Überlauf zu verursachen und müssen möglicherweise auf die Verwendung von Registern und Stapeln achten. Was Sie zu UINT16, UCHAR führen kann. Mit Typen wie UCHAR kann Compiler 'Fluff' jedoch hinzugefügt werden. Da Register normalerweise größer sind, können einige Compiler Code hinzufügen, um das Ergebnis in den Typ zu zwingen.

i++;
kann
ADD REG,1 
AND REG, 0xFF
werden, was unnötig ist.

Also ich denke, meine Frage hätte sein sollen: -

die Einschränkungen von Embedded Software gegeben, was ist die beste Politik für ein Projekt zu setzen, die viele Menschen arbeiten daran haben - nicht alle von denen der sein das gleiche Niveau an Erfahrung.

Antwort

16

Ich verwende Typ Abstraktion sehr selten. Hier sind meine Argumente, sortiert in der Reihenfolge der Subjektivität zu erhöhen:

  1. Lokale Variablen unterscheiden sich von Strukturkomponenten und Arrays in dem Sinne, dass Sie sie wollen in einem Register passen. Auf einem 32b/64b-Ziel kann ein lokaler int16_t Code im Vergleich zu einem lokalen int langsamer machen, da der Compiler Operationen zu/force/overflow entsprechend der Semantik von int16_t hinzufügen muss. Während C99 eine intfast_t Typedef definiert, passt AFAIK ein einfacher Int in ein Register genauso gut, und es ist sicher ein kürzerer Name.

  2. Organisationen, die diese typedefs fast unweigerlich mit mehreren von ihnen enden (, ad infinitum). Organisationen, die eingebaute Typen verwenden, sind somit besser dran, da sie nur einen Satz von Namen haben. Ich wünschte, dass Leute die typedefs von stdint.h oder windows.h oder irgendetwas, das existiert, benutzten; Und wenn ein Ziel diese .h-Datei nicht hat, wie schwer ist es, eine hinzuzufügen?

  3. Die Typedefs können zwar die Portabilität theoretisch unterstützen, aber ich habe nie etwas von ihnen bekommen. Gibt es ein nützliches System, das Sie von einem 32b-Ziel auf ein 16b-Ziel portieren können? Gibt es ein 16b-System, das für ein 32b-Ziel nicht trivial ist? Außerdem, wenn die meisten Vars Ints sind, werden Sie tatsächlich etwas von den 32 Bits auf dem neuen Ziel erhalten, aber wenn sie int16_t sind, werden Sie nicht. Und die Orte, die schwer zu tragen sind, erfordern ohnehin eine manuelle Inspektion; Bevor Sie einen Port versuchen, wissen Sie nicht, wo sie sind. Nun, wenn jemand denkt, dass es so einfach ist, Dinge zu portieren, wenn Sie typedefs überall haben - wenn die Zeit zum Port kommt, was mit wenigen Systemen passiert, schreiben Sie ein Skript, das alle Namen in der Codebasis konvertiert. Dies sollte nach der Logik "keine manuelle Inspektion erforderlich" funktionieren und verschiebt den Aufwand auf den Zeitpunkt, an dem er tatsächlich genutzt wird.

  4. Nun, wenn Portabilität ein theoretischer Vorteil der typedefs sein kann, Lesbarkeit geht sicher den Bach runter. Schauen Sie sich einfach stdint.h an: {int,uint}{max,fast,least}{8,16,32,64}_t. Viele Arten. Ein Programm hat viele Variablen; Ist es wirklich so einfach zu verstehen, welche int_fast16_t und welche uint_least32_t sein müssen? Wie oft werden wir still zwischen ihnen umwandeln, so dass sie völlig sinnlos sind? (Ich mag besonders BOOL/Bool/eBool/boolean/bool/int Konvertierungen. Jedes Programm, das von einer geordneten Organisation geschrieben wurde, die typedefs beauftragt, wird damit übersät).

  5. Natürlich könnten wir das Typsystem strikter machen, indem wir Zahlen in Template-Klasseninstanziierungen mit überladenen Operatoren und Zeug einwickeln. Das bedeutet, dass Sie jetzt Fehlermeldungen der Form "Klasse < int, Least, 32 > hat keine Operator + Überladung für Argument des Typs Klasse Nummer < unsigned lange lang, Fast, 64 >, Kandidaten erhalten ..." Ich Nenne das auch nicht "Lesbarkeit". Ihre Chancen, diese Wrapper-Klassen korrekt zu implementieren, sind mikroskopisch, und die meiste Zeit warten Sie darauf, dass die unzähligen Template-Instanziierungen kompiliert werden.

+0

Eine weitere Optimierung, die ich gesehen und geschätzt habe, ist die Verwendung von FIXED-Typen und "USE-BEST" -Typen. , d. H. Typedef unsigned char UINT8 typedef unsigned uint255; UINT 255 gibt den Wertebereich an, lässt aber die optimale Größe pro System angeben – itj

+0

@itj: Statt uint255 verwenden Sie 'uint_fast8_t' aus' stdint.h'. Es ist als schneller Typ definiert, der einen vorzeichenlosen 8-Bit-Wert unterstützen kann. Auf einer Plattform kann das ein "unsignierter Char" sein. Auf einem anderen kann es einfach ein "unsigned int" sein. – tomlogic

+0

Das ist alles in Ordnung, bis Ihr Code auf einem sehr begrenzten Prozessor funktioniert und Sie wollen oder müssen auf einem anderen Prozessor testen, vielleicht weil das Testen und Debuggen auf dem realen Ziel schwierig/unmöglich ist. In dieser Situation müssen Sie auf Ihrem Dev-Host mit Variablen der gleichen Größe wie auf dem Ziel testen, und wenn Sie nicht mit Größen-spezifischen Typedefs codiert haben, wird Ihr Prozess vollständig borken. – barny

1

Konsistenz, Bequemlichkeit und Lesbarkeit. "UINT32" ist viel lesbarer und beschreibbarer als "unsigned long long", was für einige Systeme gleichbedeutend ist.

Außerdem können der Compiler und der Prozessor für die Lebensdauer eines Projekts festgelegt werden, aber der Code aus diesem Projekt kann neues Leben in einem anderen Projekt finden. In diesem Fall ist es sehr praktisch, konsistente Datentypen zu haben.

7

Der C99-Standard verfügt über eine Reihe von Standardgrößen-Integer-Typen. Wenn Sie einen Compiler verwenden können, der C99 unterstützt (gcc does), finden Sie diese in <stdint.h> und Sie können sie einfach in Ihren Projekten verwenden.

Außerdem kann es in eingebetteten Projekten besonders wichtig sein, Typen als eine Art "Sicherheitsnetz" für Dinge wie Einheitenumrechnungen zu verwenden. Wenn Sie C++ verwenden können, verstehe ich, dass es einige "Unit" -Bibliotheken gibt, mit denen Sie in physischen Einheiten arbeiten können, die vom C++ - System (über Templates) definiert sind und als Operationen für die zugrunde liegenden Skalartypen kompiliert werden. In diesen Bibliotheken können Sie beispielsweise keine distance_t zu einer mass_t hinzufügen, da die Einheiten nicht ausgerichtet sind. Sie erhalten tatsächlich einen Compilerfehler.

Auch wenn Sie nicht in C++ oder einer anderen Sprache arbeiten können, in der Sie Code auf diese Weise schreiben können, können Sie zumindest das C-System verwenden, um Fehler wie diese zu erkennen. (Das war eigentlich die ursprüngliche Absicht von Simonyis ungarischer Notation.) Nur weil der Compiler Sie nicht anschreien wird, dass Sie meter_t zu einem gram_t hinzufügen, bedeutet das nicht, dass Sie solche Typen nicht verwenden sollten. Codeüberprüfungen werden dann wesentlich produktiver sein, wenn Sie Unit-Fehler entdecken.

4

Meine Meinung ist, wenn Sie auf einem Minimum/Maximum/spezifische Größe abhängig nicht nur davon ausgehen, dass (sagen wir) ein unsigned int ist 32 Bytes - verwenden uint32_t statt (vorausgesetzt, Ihr Compiler unterstützt C99).

4

Ich benutze gerne stdint.h-Typen definieren System-APIs speziell, weil sie explizit angeben, wie groß die Elemente sind. In den alten Zeiten von Palm OS wurden die System-APIs mit einer Reihe von wisch-wuschigen Typen wie "Word" und "SWord" definiert, die vom sehr klassischen Mac OS übernommen wurden. Sie haben eine Bereinigung vorgenommen, um stattdessen Int16 zu sagen, und es hat die API für Neulinge leichter verständlich gemacht, insbesondere mit den seltsamen 16-Bit-Pointer-Problemen auf diesem System. Als sie Palm OS Cobalt entwarfen, änderten sie diese Namen erneut, um sie mit den Namen von stdint.h abzugleichen, was sie noch klarer machte und die Menge an Typdefs reduzierte, die sie verwalten mussten.

+0

+1 für die Verwendung von Typen in 'stdint.h'. Der beste Weg zur Portabilität. Wenn eine Plattform es nicht hat, ist es einfach, es zu erstellen. – tomlogic

3

Ich glaube, dass MISRA-Standards vorschlagen (erforderlich?) Die Verwendung von Typedefs.

Aus einer persönlichen Perspektive lässt die Verwendung von typedefs keine Verwirrung über die Größe (in Bits/Bytes) bestimmter Typen zu. Ich habe gesehen, dass führende Entwickler versuchen, beide Wege zu entwickeln, indem sie Standardtypen verwenden, z. int und benutzerdefinierte Typen verwenden, z. UINT32.

Wenn der Code nicht tragbar ist, gibt es wenig echten Nutzen bei der Verwendung typedefs, jedoch, wenn Sie wie ich dann Sie beiden Arten von Software (portable und fest Umgebung) arbeiten, dann kann es sinnvoll sein, zu halten ein Standard und benutze die Cutomised-Typen. Zumindest wie Sie sagen, der Programmierer ist sich dann sehr bewusst, wie viel Speicher sie verwenden. Ein weiterer zu berücksichtigender Faktor ist, wie sicher Sie sind, dass der Code nicht in eine andere Umgebung portiert wird? Ich habe gesehen, dass prozessorspezifischer Code übersetzt werden muss, da ein Hardware-Entwickler plötzlich ein Board wechseln musste. Das ist keine gute Situation, aber aufgrund der benutzerdefinierten Typedefs hätte es viel schlimmer sein können!

+0

Ja, es ist eine Beratungsregel * (# 6.3 von MISRA-C 2004 bzw. # 13 von MISRA-C '98) *. – ollo

0

Vielleicht bin ich komisch, aber ich benutze ub, ui, ul, sb, si und sl für meine Integer-Typen. Vielleicht scheint das "i" für 16 Bits etwas veraltet zu sein, aber ich mag das Aussehen von ui/si besser als uw/sw.

+0

Ich denke, das hängt vom Kontext ab. Für Embedded-Programmierung ist Größe sehr wichtig, so dass i & w effektiv "egal" Werte sind. – itj

+0

@itj: Ich bin mir nicht sicher, was du damit meinst. Ich verwende meine zweistelligen Typbezeichner, weil sie kurz und visuell sauber und deutlich sind. Ich kann mir keine anderen 2-stelligen Bezeichner vorstellen, die ich für irgendeinen Zweck benutze, die mit 's' oder 'u' beginnen, daher erscheint es ziemlich offensichtlich, was die Typen bedeuten (außer vielleicht für 'ui' oder 'si') isoliert). – supercat

1

Wenn Ihre Embedded-Systeme irgendwie ein sicherheitskritischen System (oder ähnlich), es ist stark geraten (falls nicht erforderlich) typedefs über Normaltypen zu verwenden.

Als TK. vorher gesagt hat, MISRA-C hat eine (Advisory) Regel, dies zu tun:

Regel 6.3 (Advisory): typedefs, die Größe und Signedness zeigen sollte anstelle der grundlegenden numerischen Typen verwendet werden .

(von MISRA-C 2004; es ist Regel # 13 (adv) von MISRA-C 1998)


Gleiches gilt auch für C++ in diesem Bereich; z.B. JSF C++ coding standards:

AV-Regel 209 A UniversalTypes Datei alle sta ndard Typen für Entwickler zu definieren, erstellt werden zu verwenden. Die Typen sind: [uint16, int16, uint32_t etc.]

1

Mit <stdint.h> macht den Code mehr tragbar für Unit-Tests auf einem PC.

Es kann ziemlich hart beißen, wenn Sie Tests für alles haben, aber es bricht immer noch auf Ihrem Zielsystem, weil ein int plötzlich nur 16 Bit lang ist.

+0

Ja, das ist eine der pragmatischsten Demonstrationen bei der Verwendung von explizit großen Typen, die sehr sinnvoll sind. Natürlich, wenn Sie keine PC-gehosteten Tests machen, wird es Ihnen egal sein. – barny