Welche der obigen 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 Thema, ich frage mich warum zwöl subtrahiert 0x10000u von dem uint32_t Wert umgewandelt in int32_t.
Warum ist das der richtige Weg?
Wie vermeidet es das durch die Implementierung definierte Verhalten beim Konvertieren in den Rückgabetyp?
Da Sie die Komplementdarstellung von 2 annehmen können, wie würde diese einfachere Umwandlung fehlschlagen: return (uint16_t)val;
Das genaue Verhalten beim Casting zu int16_t ist implementierungsdefiniert, daher ist der naive Ansatz nicht portierbar.
– nwellnhof
26. März 2020 um 9:56 Uhr
@nwellnhof gibt es keine Besetzung int16_t
– MM
26. März 2020 um 9:57 Uhr
Die Frage im Titel kann nicht beantwortet werden, ohne anzugeben, welche Zuordnung verwendet werden soll
– MM
26. März 2020 um 10:17 Uhr
Beide Ansätze beruhen auf implementierungsdefiniertem Verhalten (Konvertieren eines vorzeichenlosen Werts in einen vorzeichenbehafteten Typ, der den Wert nicht darstellen kann). Z.B. im ersten Ansatz, 0xFFFF0001u kann nicht dargestellt werden als int16_tund im zweiten Ansatz 0xFFFFu kann nicht dargestellt werden als int16_t.
– Sander DeDycker
26. März 2020 um 10:25 Uhr
“Da Sie die Komplementdarstellung von 2 annehmen können” [citation needed]. C89 und C99 haben 1er-Komplement- und Vorzeichen-Größen-Darstellungen sicherlich nicht geleugnet. Qv, stackoverflow.com/questions/12276957/…
– Eric Türme
26. März 2020 um 21:55 Uhr
MM
Wenn int 16-Bit ist, dann verlässt sich Ihre Version auf implementierungsdefiniertes Verhalten, wenn der Wert des Ausdrucks in der return Anweisung liegt außerhalb des gültigen Bereichs für int16_t.
Die erste Version hat jedoch auch ein ähnliches Problem; zum Beispiel wenn int32_t ist ein Typdef für intund die Eingabebytes sind beides 0xFFdann ist das Ergebnis der Subtraktion in der return-Anweisung UINT_MAX was bei der Konvertierung zu implementierungsdefiniertem Verhalten führt int16_t.
IMHO hat die Antwort, auf die Sie verlinken, mehrere Hauptprobleme.
@idmean Die Frage muss geklärt werden, bevor sie beantwortet werden kann. Ich habe in einem Kommentar unter der Frage darum gebeten, aber OP hat nicht geantwortet
– MM
27. März 2020 um 8:04 Uhr
@MM: Ich habe die Frage bearbeitet, um anzugeben, dass Endianness nicht das Problem ist. IMHO ist das Problem, das zwol zu lösen versucht, das implementierungsdefinierte Verhalten beim Konvertieren in den Zieltyp, aber ich stimme Ihnen zu: Ich glaube, er irrt sich, da seine Methode andere Probleme hat. Wie würden Sie das durch die Implementierung definierte Verhalten effizient lösen?
– chqrlie
28. März 2020 um 12:32 Uhr
@chqrlieforyellowblockquotes Ich habe mich nicht speziell auf Endianness bezogen. Wollen Sie nur die genauen Bits der beiden Eingangsoktette in die int16_t ?
– MM
28. März 2020 um 13:19 Uhr
@MM: ja, genau das ist die Frage. Ich schrieb Byte aber das richtige Wort sollte ja sein Oktette wie der Typ ist uchar8_t.
– chqrlie
28. März 2020 um 13:35 Uhr
@chqrlieforyellowblockquotes OK, dann verwenden Sie eine der Lösungen, die nur die zwei Bytes schreibt (z. B. memcpy oder Zuweisung an uint8_t)
– MM
28. März 2020 um 22:26 Uhr
jpa
Dies sollte umständlich korrekt sein und auch auf Plattformen funktionieren, die dies verwenden Zeichenbit oder 1er-Komplement Darstellungen statt der üblichen 2er Komplement. Es wird davon ausgegangen, dass die Eingangsbytes im Zweierkomplement vorliegen.
int le16_to_cpu_signed(const uint8_t data[static 2]) {
unsigned value = data[0] | ((unsigned)data[1] << 8);
if (value & 0x8000)
return -(int)(~value) - 1;
else
return value;
}
Aufgrund der Verzweigung wird es teurer als andere Optionen.
Was dies erreicht, ist, dass es jede Annahme darüber vermeidet, wie int Vertretung betrifft unsigned Vertretung auf der Plattform. Die Besetzung zu int ist erforderlich, um den arithmetischen Wert für jede Zahl beizubehalten, die in den Zieltyp passt. Da die Umkehrung sicherstellt, dass das oberste Bit einer 16-Bit-Zahl Null ist, passt der Wert. Dann das Unäre - und Subtraktion von 1 wenden die übliche Regel für die 2er-Komplement-Negation an. Je nach Plattform, INT16_MIN könnte immer noch überlaufen, wenn es nicht in die passt int tippen Sie auf das Ziel, in diesem Fall long sollte benutzt werden.
Der Unterschied zur Originalversion in Frage kommt bei der Rückgabezeit. Während das Original eben immer abgezogen wird 0x10000 und das 2er-Komplement lassen Sie den vorzeichenbehafteten Überlauf umwickeln int16_t Bereich hat diese Version die explizite if das vermeidet einen signierten Wrapover (der nicht definiert ist).
In der Praxis verwenden fast alle heute verwendeten Plattformen die 2er-Komplementdarstellung. In der Tat, wenn die Plattform standardkonform ist stdint.h das definiert int32_tes muss Verwenden Sie dafür das 2er-Komplement. Wo dieser Ansatz manchmal praktisch ist, sind einige Skriptsprachen, die überhaupt keine Integer-Datentypen haben – Sie können die oben gezeigten Operationen für Gleitkommazahlen ändern und es wird das richtige Ergebnis liefern.
Der C-Standard schreibt dies ausdrücklich vor int16_t und alle intxx_t und ihre vorzeichenlosen Varianten müssen die 2er-Komplementdarstellung ohne Füllbits verwenden. Es würde eine absichtlich perverse Architektur erfordern, um diese Typen zu hosten und eine andere Darstellung dafür zu verwenden intaber ich denke, der DS9K könnte so konfiguriert werden.
– chqrlie
27. März 2020 um 7:24 Uhr
@chqrlieforyellowblockquotes Guter Punkt, ich habe mich für die Verwendung geändert int um die Verwirrung zu vermeiden. In der Tat, wenn die Plattform definiert int32_t es muss das Zweierkomplement sein.
– jpa
27. März 2020 um 7:30 Uhr
Diese Typen wurden in C99 folgendermaßen standardisiert: C99 7.18.1.1 Integer-Typen mit exakter BreiteDer Typedef-Name intN_t bezeichnet einen vorzeichenbehafteten ganzzahligen Typ mit Breite N, keine Füllbits und eine Zweierkomplementdarstellung. Daher, int8_t bezeichnet einen vorzeichenbehafteten Integer-Typ mit einer Breite von genau 8 Bit. Andere Darstellungen werden weiterhin vom Standard unterstützt, jedoch für andere Integer-Typen.
– chqrlie
27. März 2020 um 7:30 Uhr
Mit Ihrer aktualisierten Version (int)value hat implementierungsdefiniertes Verhalten, wenn Typ int hat nur 16 Bit. Ich fürchte, Sie müssen verwenden (long)value - 0x10000aber auf Nicht-2er-Komplement-Architekturen der Wert 0x8000 - 0x10000 kann nicht als 16-Bit dargestellt werden intdas Problem bleibt also.
– chqrlie
27. März 2020 um 7:32 Uhr
@chqrlieforyellowblockquotes Ja, habe gerade dasselbe bemerkt, ich habe es stattdessen mit ~ behoben, aber long würde genauso gut funktionieren.
@MaximEgorushkin: Wikipedia ist keine maßgebliche Quelle für die Interpretation des C-Standards.
– Eric Postpischil
26. März 2020 um 11:46 Uhr
@EricPostpischil Es ist unklug, sich eher auf den Boten als auf die Nachricht zu konzentrieren.
– Maxim Egorushkin
26. März 2020 um 13:57 Uhr
@MaximEgorushkin: Oh ja, oops, ich habe deinen Kommentar falsch gelesen. Vorausgesetzt byte[2] und int16_t gleich groß sind, handelt es sich um die eine oder andere der beiden möglichen Ordnungen, nicht um willkürlich gemischte bitweise Stellenwerte. So können Sie zumindest zur Kompilierzeit erkennen, welche Endianness die Implementierung hat.
– Peter Cordes
26. März 2020 um 20:29 Uhr
Der Standard besagt eindeutig, dass der Wert des Union-Mitglieds das Ergebnis der Interpretation der gespeicherten Bits im Mitglied als Wertdarstellung dieses Typs ist. Implementierungsdefinierte Aspekte gibt es insofern, als die Repräsentation von Typen implementierungsdefiniert ist.
– MM
26. März 2020 um 22:42 Uhr
Maxim Egorushkin
Die arithmetischen Operatoren Wechsel und bitweise-oder im Ausdruck (uint16_t)data[0] | ((uint16_t)data[1] << 8) Arbeiten Sie nicht an Typen, die kleiner als sind intdamit diese uint16_t Werte werden gefördert int (oder unsigned wenn sizeof(uint16_t) == sizeof(int)). Trotzdem sollte das 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 nach Little-Endian (unter der Annahme einer Little-Endian-CPU) ist:
memcpy wird verwendet, um die Darstellung von zu kopieren int16_t und das ist der normkonforme Weg. Diese Version kompiliert auch in 1 Anweisung movbesehen Montage.
Maxim Egorushkin
Hier ist eine andere Version, die nur auf portable und wohldefinierte Verhaltensweisen (header #include <endian.h> ist nicht Standard, der Code lautet):
Die Little-Endian-Version wird zu Single kompiliert movbe Anleitung mit clang, gcc Version ist weniger optimal, siehe Montage.
@chqrlieforyellowblockquotes Ihr Hauptanliegen scheint gewesen zu sein uint16_t zu int16_t Konvertierung, diese Version hat diese Konvertierung nicht, also los geht’s.
– Maxim Egorushkin
30. März 2020 um 15:15 Uhr
Ich möchte allen Mitwirkenden für ihre Antworten danken. Darauf läuft das Kollektivwerk hinaus:
Nach C-Norm 7.20.1.1 Integer-Typen mit exakter Breite: Typen uint8_t, int16_t und uint16_t muss die Zweierkomplementdarstellung ohne Füllbits verwenden, sodass die tatsächlichen Bits der Darstellung eindeutig die der 2 Bytes im Array sind, in der Reihenfolge, die durch die Funktionsnamen angegeben ist.
Berechnung des vorzeichenlosen 16-Bit-Werts mit (unsigned)data[0] | ((unsigned)data[1] << 8) (für die Little-Endian-Version) wird zu einer einzelnen Anweisung kompiliert und ergibt einen vorzeichenlosen 16-Bit-Wert.
Nach C-Norm 6.3.1.3 Ganzzahlen mit und ohne Vorzeichen: Konvertieren eines Werts vom Typ uint16_t auf signierten Typ int16_t weist implementierungsdefiniertes Verhalten auf, wenn der Wert nicht im Bereich des Zieltyps liegt. Für Typen, deren Repräsentation genau definiert ist, sind keine besonderen Vorkehrungen getroffen.
Um dieses implementierungsdefinierte Verhalten zu vermeiden, kann man testen, ob der vorzeichenlose Wert größer als ist INT_MAX und den entsprechenden vorzeichenbehafteten Wert durch Subtrahieren berechnen 0x10000. Tun Sie dies für alle Werte, wie von vorgeschlagen zwöl kann Werte außerhalb des Bereichs von erzeugen int16_t mit dem gleichen implementierungsdefinierten Verhalten.
Prüfung für die 0x8000 bit bewirkt explizit, dass die Compiler ineffizienten Code erzeugen.
eine effizientere Konvertierung ohne Implementierung definierter Verhaltensweisen tippe wortspiel über eine Gewerkschaft, aber die Debatte über die Definiertheit dieses Ansatzes ist noch offen, sogar auf der Ebene des C-Standard-Ausschusses.
tippe wortspiel kann portabel und mit definiertem Verhalten ausgeführt werden memcpy.
Durch die Kombination der Punkte 2 und 7 ist hier eine portable und vollständig definierte Lösung, die mit beiden effizient zu einer einzigen Anweisung kompiliert werden kann gcc und klirren:
be16_to_cpu_signed(unsigned char const*):
movbe ax, WORD PTR [rdi]
ret
le16_to_cpu_signed(unsigned char const*):
movzx eax, WORD PTR [rdi]
ret
@chqrlieforyellowblockquotes Ihr Hauptanliegen scheint gewesen zu sein uint16_t zu int16_t Konvertierung, diese Version hat diese Konvertierung nicht, also los geht’s.
– Maxim Egorushkin
30. März 2020 um 15:15 Uhr
Tyler Streeter
Warum nicht einfach Ihre “naive Lösung” verwenden, sondern jedes Element umwandeln int16_t Anstatt von uint16_t?
Das genaue Verhalten beim Casting zu
int16_t
ist implementierungsdefiniert, daher ist der naive Ansatz nicht portierbar.– nwellnhof
26. März 2020 um 9:56 Uhr
@nwellnhof gibt es keine Besetzung
int16_t
– MM
26. März 2020 um 9:57 Uhr
Die Frage im Titel kann nicht beantwortet werden, ohne anzugeben, welche Zuordnung verwendet werden soll
– MM
26. März 2020 um 10:17 Uhr
Beide Ansätze beruhen auf implementierungsdefiniertem Verhalten (Konvertieren eines vorzeichenlosen Werts in einen vorzeichenbehafteten Typ, der den Wert nicht darstellen kann). Z.B. im ersten Ansatz,
0xFFFF0001u
kann nicht dargestellt werden alsint16_t
und im zweiten Ansatz0xFFFFu
kann nicht dargestellt werden alsint16_t
.– Sander DeDycker
26. März 2020 um 10:25 Uhr
“Da Sie die Komplementdarstellung von 2 annehmen können” [citation needed]. C89 und C99 haben 1er-Komplement- und Vorzeichen-Größen-Darstellungen sicherlich nicht geleugnet. Qv, stackoverflow.com/questions/12276957/…
– Eric Türme
26. März 2020 um 21:55 Uhr