Ich schreibe eine Anwendung in c für einen STM32F105 mit gcc.
In der Vergangenheit (mit einfacheren Projekten), habe ich immer Variablen wie definiert char
, int
, unsigned int
, und so weiter.
Ich sehe , dass es üblich ist , die Typen in stdint.h, wie definiert zu verwenden int8_t
, uint8_t
, uint32_t
usw. Dies freilich in mehreren APIs , dass ich verwende, und auch in der ARM CMSIS Bibliothek von ST.
Ich glaube, ich verstehe, warum wir das tun sollten. um es dem Compiler zu ermöglichen, den Speicherplatz besser zu optimieren. Ich gehe davon aus, dass es weitere Gründe geben kann.
Aufgrund der Regeln für die Heraufstufung von Ganzzahlen in c stoße ich jedoch immer wieder auf Konvertierungswarnungen, wenn ich versuche, zwei Werte hinzuzufügen, eine bitweise Operation durchzuführen usw. Die Warnung lautet etwa conversion to 'uint16_t' from 'int' may alter its value [-Wconversion]
. Das Thema wird hier und hier diskutiert .
Dies passiert nicht, wenn Variablen verwendet werden, die als int
oder deklariert sind unsigned int
.
Um einige Beispiele zu nennen:
uint16_t value16;
uint8_t value8;
Das müsste ich ändern:
value16 <<= 8;
value8 += 2;
dazu:
value16 = (uint16_t)(value16 << 8);
value8 = (uint8_t)(value8 + 2);
Es ist hässlich, aber ich kann es tun, wenn nötig. Hier sind meine Fragen:
Gibt es einen Fall, in dem die Konvertierung von nicht signiert zu signiert und zurück zu nicht signiert das Ergebnis falsch macht?
Gibt es andere wichtige Gründe für / gegen die Verwendung der Integer-Typen stdint.h?
Basierend auf den Antworten, die ich erhalte, sieht es so aus, als ob die stdint.h-Typen im Allgemeinen bevorzugt werden, obwohl c uint
nach int
und zurück konvertiert wird . Dies führt zu einer größeren Frage:
- Ich kann die Compiler-Warnungen durch Typecasting (zB
value16 = (uint16_t)(value16 << 8);
) verhindern. Verstecke ich nur das Problem? Gibt es einen besseren Weg, dies zu tun?
8u
und2u
.value8 += 2u;
undvalue8 = value8 + 2u;
, aber ich bekomme die gleichen Warnungen.Antworten:
Ein normenkonformer Compiler mit
int
17 bis 32 Bit kann mit folgendem Code legitimerweise alles tun, was er will :Eine Implementierung, die dies wollte, konnte zu Recht ein Programm generieren, das nichts anderes tun würde, als die Zeichenfolge "Fred" wiederholt auf jedem Port-Pin unter Verwendung jedes erdenklichen Protokolls auszugeben. Die Wahrscheinlichkeit, dass ein Programm auf eine Implementierung portiert wird, die so etwas tun würde, ist außergewöhnlich gering, aber theoretisch möglich. Wenn der obige Code so geschrieben werden soll, dass garantiert kein undefiniertes Verhalten auftritt, muss der letztere Ausdruck als
(uint32_t)x*x
oder geschrieben werden1u*x*x
. Auf einem Compiler mitint
17 bis 31 Bits würde der letztgenannte Ausdruck die oberen Bits entfernen, sich jedoch nicht auf Undefiniertes Verhalten einlassen.Ich denke, dass die gcc-Warnungen wahrscheinlich darauf hindeuten, dass der geschriebene Code nicht zu 100% portierbar ist. Es gibt Zeiten, in denen Code wirklich geschrieben werden sollte, um Verhaltensweisen zu vermeiden, die bei einigen Implementierungen undefiniert wären. In vielen anderen Fällen sollte man jedoch einfach herausfinden, dass der Code bei Implementierungen, die übermäßig lästige Dinge bewirken, wahrscheinlich nicht verwendet wird.
Beachten Sie, dass die Verwendung von Typen wie
int
undshort
einige Warnungen beseitigen und einige Probleme beheben kann, wahrscheinlich jedoch andere verursacht. Die Interaktion zwischen Typen wieuint16_t
und Cs Regeln zur Heraufstufung von Ganzzahlen ist icky, aber solche Typen sind wahrscheinlich immer noch besser als jede Alternative.quelle
1) Wenn Sie nur von einer vorzeichenlosen zu einer vorzeichenbehafteten Ganzzahl gleicher Länge hin und her wandeln, ohne dass dazwischen Operationen ausgeführt werden müssen, erhalten Sie jedes Mal das gleiche Ergebnis, also hier kein Problem. Verschiedene logische und arithmetische Operationen wirken sich jedoch auf vorzeichenbehaftete und vorzeichenlose Operanden unterschiedlich aus.
2) Der Hauptgrund zur Verwendung
stdint.h
Arten ist , dass die Bit - Größe eines solchen Typen definiert sind und gleiche über alle Plattformen, die nicht wahr istint
,long
etc, sowiechar
hat keinen Standard signess, kann es mit oder ohne Vorzeichen werden durch Standard. Es erleichtert die Manipulation der Daten, wenn die genaue Größe bekannt ist, ohne dass zusätzliche Überprüfungen und Annahmen erforderlich sind.quelle
int32_t
unduint32_t
sind auf allen Plattformen, auf denen sie definiert sind, gleich . Wenn der Prozessor keinen genau passenden Hardwaretyp hat, sind diese Typen nicht definiert. Daher der Vorteil vonint
usw. und vielleichtint_least32_t
usw.Da Eugenes Nr. 2 wahrscheinlich der wichtigste Punkt ist, möchte ich nur hinzufügen, dass es ein Hinweis ist
Auch Jack Ganssle scheint ein Befürworter dieser Regel zu sein: http://www.ganssle.com/tem/tem265.html
quelle
uint32_t
.Eine einfache Möglichkeit, die Warnungen zu beseitigen, besteht darin, die Verwendung von -Wconversion in GCC zu vermeiden. Ich denke, Sie müssen diese Option manuell aktivieren, aber wenn nicht, können Sie -Wno-Konvertierung verwenden, um es zu deaktivieren. Sie können Warnungen für Vorzeichen- und FP-Präzisionskonvertierungen über andere Optionen aktivieren , wenn Sie diese weiterhin benötigen.
Die -Wandlungswarnungen sind fast immer falsch positiv, weshalb wahrscheinlich nicht einmal -Wextra sie standardmäßig aktiviert. Eine Stapelüberlauf-Frage enthält viele Vorschläge für gute Optionssätze. Nach meinen eigenen Erfahrungen ist dies ein guter Anfang:
Fügen Sie mehr hinzu, wenn Sie sie brauchen, aber wahrscheinlich nicht.
Wenn Sie -Wconversion beibehalten müssen, können Sie den Code ein wenig verkürzen, indem Sie nur den numerischen Operanden tippen:
Das ist jedoch ohne Hervorhebung der Syntax nicht einfach zu lesen.
quelle
In jedem Softwareprojekt ist es sehr wichtig, tragbare Typdefinitionen zu verwenden. (Auch die nächste Version desselben Compilers benötigt diese Überlegung.) Ein gutes Beispiel: Vor einigen Jahren habe ich ein Projekt bearbeitet, in dem der aktuelle Compiler 'int' als 8 Bit definiert hat. In der nächsten Version des Compilers wurde 'int' als 16 Bit definiert. Da wir keine portablen Definitionen für 'int' verwendet hatten, verdoppelte sich die Größe des RAM (effektiv), und viele Codesequenzen, die von einem 8-Bit-Int abhängig waren, scheiterten. Die Verwendung einer portablen Typdefinition hätte dieses Problem (Hunderte von Arbeitsstunden) vermieden.
quelle
int
, um auf einen 8-Bit-Typ zu verweisen. Selbst wenn ein Nicht-C-Compiler wie CCS dies tut, sollte angemessener Code entwederchar
einen typisierten Typ für 8 Bit und einen typisierten Typ (nicht "lang") für 16 Bit verwenden. Auf der anderen Seite ist das Portieren von Code von so etwas wie CCS auf einen echten Compiler problematisch, selbst wenn geeignete Typedefs verwendet werden, da solche Compiler auf andere Weise häufig "ungewöhnlich" sind.Ja. Eine Ganzzahl mit n-Bit-Vorzeichen kann ungefähr die Hälfte der Anzahl nicht negativer Zahlen als Ganzzahl ohne n-Bit-Vorzeichen darstellen. Wenn Sie sich auf Überlaufmerkmale verlassen, ist dies undefiniertes Verhalten, sodass alles passieren kann. Die überwiegende Mehrheit der aktuellen und früheren Prozessoren verwendet Zweierkomplemente, sodass viele Operationen für vorzeichenbehaftete und vorzeichenlose Integraltypen dasselbe tun, aber selbst dann führen nicht alle Operationen zu bitweise identischen Ergebnissen. Sie werden später wirklich um zusätzliche Probleme bitten, wenn Sie nicht herausfinden können, warum Ihr Code nicht wie beabsichtigt funktioniert.
Während int und unsigned implementierungsdefinierte Größen haben, werden diese häufig von der Implementierung aus Gründen der Größe oder Geschwindigkeit "intelligent" ausgewählt. Ich halte mich im Allgemeinen an diese, es sei denn, ich habe einen guten Grund, etwas anderes zu tun. Wenn ich überlege, ob ich int oder unsigned verwenden soll, bevorzuge ich im Allgemeinen int, es sei denn, ich habe einen guten Grund, dies anders zu tun.
In Fällen, in denen ich wirklich eine bessere Kontrolle über die Größe oder Signiertheit eines Typs benötige, bevorzuge ich normalerweise die Verwendung eines systemdefinierten typedef (size_t, intmax_t usw.) oder erstelle einen eigenen typedef, der die Funktion eines bestimmten Typs angibt Typ (prng_int, adc_int usw.).
quelle
Oft wird der Code auf ARM Thumb und AVR (und x86, PowerPC und anderen Architekturen) verwendet, und 16 oder 32 Bit können auf STM32 ARM effizienter sein (in beide Richtungen: Flash und Zyklen), selbst für eine Variable, die in 8 Bit passt ( 8 Bit ist bei AVR effizienter) . Wenn der SRAM jedoch fast voll ist, kann das Zurücksetzen auf 8 Bit für globale Variablen sinnvoll sein (nicht jedoch für lokale Variablen). Für die Portabilität und Wartung (insbesondere für 8-Bit-Versionen) hat es Vorteile (ohne Nachteile) , die MINIMUM-geeignete Größe anstelle der exakten Größe und der Typendefinition an einer Stelle (normalerweise unter ifdef) anzugeben , um die Einstellung vorzunehmen (möglicherweise uint_fast8_t /). uint_least8_t) während der Portierung / Build-Zeit, zB:
Die GNU-Bibliothek hilft ein bisschen, aber normalerweise sind die typedefs trotzdem sinnvoll:
// aber uint_fast8_t für BEIDE, wenn SRAM kein Problem ist.
quelle