In dieser Antwort machte zwol diese Behauptung:
Die korrekte Methode zum Konvertieren von zwei Datenbytes von einer externen Quelle in eine 16-Bit-Ganzzahl mit Vorzeichen besteht in folgenden Hilfsfunktionen:
#include <stdint.h>
int16_t be16_to_cpu_signed(const uint8_t data[static 2]) {
uint32_t val = (((uint32_t)data[0]) << 8) |
(((uint32_t)data[1]) << 0);
return ((int32_t) val) - 0x10000u;
}
int16_t le16_to_cpu_signed(const uint8_t data[static 2]) {
uint32_t val = (((uint32_t)data[0]) << 0) |
(((uint32_t)data[1]) << 8);
return ((int32_t) val) - 0x10000u;
}
Welche der oben genannten Funktionen geeignet ist, hängt davon ab, ob das Array eine Little-Endian- oder eine Big-Endian-Darstellung enthält. Endianness ist hier nicht das fragliche Thema, ich frage mich, warum zwol0x10000u
von dem uint32_t
umgerechneten Wert subtrahiert int32_t
.
Warum ist das der richtige Weg ?
Wie wird das implementierungsdefinierte Verhalten bei der Konvertierung in den Rückgabetyp vermieden?
Wie würde diese einfachere Besetzung scheitern, da Sie die Komplementdarstellung von 2 annehmen können: return (uint16_t)val;
Was ist los mit dieser naiven Lösung:
int16_t le16_to_cpu_signed(const uint8_t data[static 2]) {
return (uint16_t)data[0] | ((uint16_t)data[1] << 8);
}
quelle
int16_t
ist implementierungsdefiniert, sodass der naive Ansatz nicht portierbar ist.int16_t
0xFFFF0001u
kann nicht als dargestellt werdenint16_t
, und im zweiten Ansatz0xFFFFu
kann nicht als dargestellt werdenint16_t
.Antworten:
Wenn
int
es sich um 16-Bit handelt, stützt sich Ihre Version auf ein implementierungsdefiniertes Verhalten, wenn der Wert des Ausdrucks in derreturn
Anweisung außerhalb des Bereichs für liegtint16_t
.Die erste Version hat jedoch auch ein ähnliches Problem; Wenn
int32_t
beispielsweise ein typedef fürint
ist und die Eingabebytes beide sind0xFF
, führt das Ergebnis der Subtraktion in der return-AnweisungUINT_MAX
zu einem implementierungsdefinierten Verhalten bei der Konvertierung inint16_t
.IMHO hat die Antwort, auf die Sie verlinken, mehrere Hauptprobleme.
quelle
int16_t
?uchar8_t
.Dies sollte pedantisch korrekt sein und auch auf Plattformen funktionieren, die Vorzeichenbit- oder 1-Komplementdarstellungen anstelle des üblichen 2-Komplements verwenden . Es wird angenommen, dass die Eingangsbytes im Zweierkomplement sind.
Aufgrund der Branche ist es teurer als andere Optionen.
Dies führt dazu, dass jegliche Annahme vermieden wird, wie sich
int
Repräsentation aufunsigned
Repräsentation auf der Plattform bezieht . Die Umwandlung inint
ist erforderlich, um den arithmetischen Wert für jede Zahl beizubehalten, die in den Zieltyp passt. Da die Inversion sicherstellt, dass das oberste Bit der 16-Bit-Zahl Null ist, passt der Wert. Dann-
wenden die Unäre und die Subtraktion von 1 die übliche Regel für die Komplementnegation von 2 an. Je nach Plattform kannINT16_MIN
es immer noch zu einem Überlauf kommen, wenn es nicht in denint
Typ auf dem Ziel passt. In diesem Falllong
sollte es verwendet werden.Der Unterschied zur Originalversion in der Frage ergibt sich aus der Rückgabezeit. Während das Original nur immer subtrahiert wird
0x10000
und das 2er-Komplement den signierten Überlauf in denint16_t
Bereichif
umschließt , hat diese Version das explizite , das signierte Wrapover vermeidet (was undefiniert ist ).In der Praxis verwenden heute fast alle heute verwendeten Plattformen die Komplementdarstellung von 2. In der Tat, wenn die Plattform standardkonform ist
stdint.h
definiertint32_t
, muss sie das 2er-Komplement verwenden. Manchmal ist dieser Ansatz bei einigen Skriptsprachen nützlich, die überhaupt keine ganzzahligen Datentypen haben. Sie können die oben gezeigten Vorgänge für Floats ändern und erhalten das richtige Ergebnis.quelle
int16_t
und alleintxx_t
und ihre vorzeichenlosen Varianten die Zweierkomplementdarstellung ohne Auffüllbits verwenden müssen. Es würde eine absichtlich perverse Architekturint
erfordern , um diese Typen zu hosten und eine andere Darstellung zu verwenden , aber ich denke, der DS9K könnte auf diese Weise konfiguriert werden.int
, um die Verwirrung zu vermeiden. In der Tat, wenn die Plattform definiertint32_t
, muss es die Ergänzung von 2 sein.intN_t
bezeichnet einen vorzeichenbehafteten Ganzzahltyp mit BreiteN
, keinen Füllbits und einer Zweierkomplementdarstellung. Somitint8_t
bezeichnet einen vorzeichenbehaftete Ganzzahl - Typen mit einer Breite von genau 8 Bits. Andere Darstellungen werden vom Standard weiterhin unterstützt, jedoch für andere Ganzzahltypen.(int)value
Hat mit Ihrer aktualisierten Version das Implementierungsverhalten definiert, wenn der Typint
nur 16 Bit hat? Ich fürchte, Sie müssen verwenden(long)value - 0x10000
, aber auf Komplement-Architekturen von Nicht-2 kann der Wert0x8000 - 0x10000
nicht als 16-Bit dargestellt werdenint
, sodass das Problem bestehen bleibt.long
würde aber genauso gut funktionieren.Eine andere Methode - mit
union
:Im Programm:
first_byte
undsecond_byte
kann nach Little- oder Big-Endian-Modell getauscht werden. Diese Methode ist nicht besser, aber eine der Alternativen.quelle
byte[2]
undint16_t
die gleiche Größe haben , ist es das eine oder das andere der beiden möglichen Anordnungen, nicht einige willkürlich gemischt bitweise Stellenwerte. So können Sie zumindest zur Kompilierungszeit feststellen, welche Endianness die Implementierung hat.Die arithmetischen Operatoren verschieben sich und bitweise - oder im Ausdruck -
(uint16_t)data[0] | ((uint16_t)data[1] << 8)
funktionieren nicht bei Typen, die kleiner als sindint
, sodass dieseuint16_t
Werte aufint
(oder) hochgestuft werdenunsigned
wennsizeof(uint16_t) == sizeof(int)
) heraufgestuft werden . Dies sollte jedoch die richtige Antwort liefern, da nur die unteren 2 Bytes den Wert enthalten.Eine andere pedantisch korrekte Version für die Konvertierung von Big-Endian in Little-Endian (unter der Annahme einer Little-Endian-CPU) ist:
memcpy
wird verwendet, um die Darstellung von zu kopieren,int16_t
und dies ist die standardkonforme Methode, um dies zu tun. Diese Version wird auch in 1 Anweisung kompiliertmovbe
, siehe Assembly .quelle
__builtin_bswap16
dafür ist, dass Byte-Swapping in ISO C nicht so effizient implementiert werden kann.int16_t
inuint16_t
ist gut definiert: Negative Werte werden in Werte konvertiert, die größer als sindINT_MAX
, aber die Konvertierung dieser Werte inuint16_t
ist ein implementierungsdefiniertes Verhalten: 6.3.1.3 Vorzeichenbehaftete und vorzeichenlose Ganzzahlen 1. Wenn ein Wert mit Ganzzahltyp in einen anderen Ganzzahltyp als_Bool konvertiert wird, wenn Der Wert kann durch den neuen Typ dargestellt werden, er bleibt unverändert. ... 3. Andernfalls wird der neue Typ signiert und der Wert kann nicht darin dargestellt werden. Entweder ist das Ergebnis implementierungsdefiniert oder es wird ein implementierungsdefiniertes Signal ausgelöst.ntohs
/__builtin_bswap
und das|
/<<
pattern: gcc.godbolt.org/z/rJ-j87Hier ist eine andere Version, die sich nur auf tragbare und genau definierte Verhaltensweisen stützt (Header
#include <endian.h>
ist nicht Standard, der Code ist):Die Little-Endian - Version kompiliert auf einzelne
movbe
Anweisung mitclang
,gcc
Version ist weniger optimal, siehe Montage .quelle
uint16_t
umint16_t
Umwandlung, diese Version nicht , dass die Umwandlung nicht haben, können Sie so hier.Ich möchte allen Mitwirkenden für ihre Antworten danken. Das, worauf die kollektiven Werke hinauslaufen:
uint8_t
,int16_t
unduint16_t
, ohne Füllbits Komplementdarstellung Zweierkomplement verwenden muß , so dass die tatsächlichen Bits der Darstellung eindeutig diejenige des 2 Bytes in der Anordnung sind, in der angegebenen Reihenfolge durch die Funktionsnamen.(unsigned)data[0] | ((unsigned)data[1] << 8)
(für die Little-Endian-Version) wird zu einem einzelnen Befehl kompiliert und ergibt einen vorzeichenlosen 16-Bit-Wert.uint16_t
in einen vorzeichenbehafteten Typint16_t
hat die Implementierung ein definiertes Verhalten, wenn der Wert nicht im Bereich des Zieltyps liegt. Für Typen, deren Darstellung genau definiert ist, sind keine besonderen Vorkehrungen getroffen.INT_MAX
und den entsprechenden vorzeichenbehafteten Wert durch Subtrahieren berechnen0x10000
. Wenn Sie dies für alle von zwol vorgeschlagenen Werte tun, können Werte außerhalb des Bereichs von erzeugt werdenint16_t
mit demselben implementierungsdefinierten Verhalten erzeugt.0x8000
explizite Testen des Bits führt dazu, dass die Compiler ineffizienten Code erzeugen.memcpy
.In Kombination der Punkte 2 und 7 finden Sie hier eine tragbare und vollständig definierte Lösung, die effizient zu einer einzelnen Anweisung mit gcc und clang kompiliert werden kann :
64-Bit-Assembly :
quelle
char
Typen können Alias oder die Objektdarstellung eines anderen Typs enthalten.uint16_t
ist nicht einer derchar
Typen, so dass dasmemcpy
vonuint16_t
toint16_t
kein genau definiertes Verhalten ist. Der Standard erfordert nur einechar[sizeof(T)] -> T > char[sizeof(T)]
Konvertierung mitmemcpy
einer genauen Definition.memcpy
ofuint16_t
toint16_t
ist bestenfalls implementierungsdefiniert, nicht portabel, nicht genau definiert, genau wie die Zuordnung von einem zum anderen, und das kann man mit nicht magisch umgehenmemcpy
. Es spielt keine Rolle, obuint16_t
die Zweierkomplementdarstellung verwendet wird oder nicht oder ob Füllbits vorhanden sind oder nicht - das ist kein Verhalten, das vom C-Standard definiert oder verlangt wird.r = u
,memcpy(&r, &u, sizeof u)
aber die letztere ist nicht besser als die erstere, oder?