19 Stimmen

C++: Umwandlung von 2 Bytes in einem Array in eine vorzeichenlose Kurzbezeichnung

Ich habe an einer C++-Legacy-Anwendung gearbeitet und befinde mich definitiv außerhalb meiner Komfortzone (was gut ist). Ich habe mich gefragt, ob jemand da draußen so freundlich wäre, mir ein paar Tipps zu geben (Wortspiel beabsichtigt).

Ich muss 2 Bytes in einem vorzeichenlosen Char-Array in ein vorzeichenloses Short umwandeln. Die Bytes sind fortlaufend.

Ein Beispiel dafür, was ich zu tun versuche:

Ich empfange eine Zeichenkette von einem Socket und lege sie in ein unsigned char-Array. Ich kann das erste Byte ignorieren und die nächsten 2 Bytes sollten dann in ein vorzeichenloses Zeichen umgewandelt werden. Dies wird auf Windows nur sein, so gibt es keine Big/Little Endian Probleme (die ich bewusst bin).

Hier ist, was ich jetzt habe (funktioniert natürlich nicht):

//packetBuffer is an unsigned char array containing the string "123456789" for testing
//I need to convert bytes 2 and 3 into the short, 2 being the most significant byte
//so I would expect to get 515 (2*256 + 3) instead all the code I have tried gives me
//either errors or 2 (only converting one byte
unsigned short myShort;
myShort = static_cast<unsigned_short>(packetBuffer[1])

0 Stimmen

Was muss dieser kerl jetzt von uns c++ programmierern denken. jeder hat eine andere "richtige" lösung :D

0 Stimmen

Nun, er könnte damit meinen, dass wir C++-Anwender ein schlaues Völkchen sind und die Regeln nach Belieben ändern. Mu ha haaa.

0 Stimmen

Enthält die Eingabe eine Zeichenkette mit Werten von '0'-'9' oder enthält sie Bytes mit Werten von 0-255? Die Dokumentation sagt String, aber es macht keinen Sinn, in diesem Fall mit 256 zu multiplizieren.

24voto

Johannes Schaub - litb Punkte 479831

Nun, du erweiterst das Zeichen zu einem kurzen Wert. Was Sie wollen, ist, zwei Bytes als Short zu interpretieren. static_cast kann nicht werfen von unsigned char* a unsigned short* . Sie müssen werfen, um void* dann an unsigned short* :

unsigned short *p = static_cast<unsigned short*>(static_cast<void*>(&packetBuffer[1]));

Nun kann man p derefenzieren und den kurzen Wert erhalten. Das Problem bei diesem Ansatz ist jedoch, dass man von unsigned char* auf void* und dann auf einen anderen Typ castet. Der Standard garantiert nicht, dass die Adresse dieselbe bleibt (und außerdem wäre die Dereferenzierung dieses Zeigers ein undefiniertes Verhalten). Ein besserer Ansatz ist die Verwendung von Bit-Shifting, was immer funktionieren wird:

unsigned short p = (packetBuffer[1] << 8) | packetBuffer[2];

2 Stimmen

Der Shift-Teil ist der richtige Weg, um dies zuverlässig über alle Hardwaretypen hinweg zu bewerkstelligen. Aber die Offsets sind 0 und 1, nicht 1 und 2 - ich werde das gleich ändern.

3 Stimmen

Und diese (und die anderen Antworten) setzen eine Endian-ness voraus - big-endian, denke ich.

2 Stimmen

Jonathan, deine Bearbeitung ist falsch. Er wollte 2 und 3 drin haben, nicht 1 und 2.

4voto

ctacke Punkte 65813

Das ist wahrscheinlich weit unter dem, was Sie interessiert, aber bedenken Sie, dass Sie auf diese Weise leicht einen nicht ausgerichteten Zugriff erhalten können. x86 ist nachsichtig und der Abbruch, den der nicht ausgerichtete Zugriff verursacht, wird intern abgefangen und endet mit einer Kopie und Rückgabe des Wertes, so dass Ihre Anwendung nichts anderes bemerkt (obwohl es deutlich langsamer ist als ein ausgerichteter Zugriff). Wenn jedoch dieser Code auf einem nicht-x86 (Sie erwähnen nicht die Zielplattform, so dass ich annehme, x86-Desktop-Windows) laufen wird, dann tun dies einen Prozessor Daten Abort verursachen und Sie müssen manuell die Daten zu einer ausgerichteten Adresse kopieren, bevor Sie versuchen, es zu werfen.

Kurz gesagt, wenn Sie diesen Zugriff häufig durchführen werden, sollten Sie den Code so anpassen, dass keine unausgerichteten Lesevorgänge auftreten, und Sie werden einen Leistungsvorteil feststellen.

0 Stimmen

Sie müssen nicht kopieren, Sie können stattdessen die Bitverschiebungsoperationen durchführen.

0 Stimmen

@Jonathan: Ja, aber es erfordert immer noch eine Zuweisung an eine andere Variable, die eine Kopie ist.

3voto

PiNoYBoY82 Punkte 1568
unsigned short myShort = *(unsigned short *)&packetBuffer[1];

3voto

old_timer Punkte 65318

Die obige Bitverschiebung hat einen Fehler:

unsigned short p = (packetBuffer[1] << 8) | packetBuffer[2];

wenn packetBuffer in Bytes (8 Bits breit) ist, kann und wird die obige Verschiebung zu packetBuffer in eine Null verwandelt, so dass Sie nur noch packetBuffer[2];

Trotzdem wird dies immer noch den Zeigern vorgezogen. Um das obige Problem zu vermeiden, verschwende ich ein paar Zeilen Code (anders als ganz-literal-zero-Optimierung) es führt zu den gleichen Maschinencode:

unsigned short p;
p = packetBuffer[1]; p <<= 8; p |= packetBuffer[2];

Oder um einige Taktzyklen zu sparen und die Bits nicht nach hinten zu verschieben:

unsigned short p;
p = (((unsigned short)packetBuffer[1])<<8) | packetBuffer[2];

Mit Zeigern muss man vorsichtig sein, der Optimierer wird einen beißen, ebenso wie mit Speicherausrichtungen und einer langen Liste anderer Probleme. Ja, wenn man es richtig macht, ist es schneller, wenn man es falsch macht, kann der Fehler lange verweilen und dann auftreten, wenn man es am wenigsten möchte.

Angenommen, Sie waren faul und wollten eine 16-Bit-Mathematik mit einem 8-Bit-Array durchführen. (Little Endian)

unsigned short *s;
unsigned char b[10];

s=(unsigned short *)&b[0];

if(b[0]&7)
{
   *s = *s+8;
   *s &= ~7;
}

do_something_With(b);

*s=*s+8;

do_something_With(b);

*s=*s+8;

do_something_With(b);

Es gibt keine Garantie dafür, dass ein vollkommen fehlerfreier Compiler den von Ihnen erwarteten Code erzeugt. Das Byte-Array b die an den do_something_with() Funktion darf niemals von der *s Operationen. Nichts im obigen Code besagt, dass dies der Fall sein sollte. Wenn Sie Ihren Code nicht optimieren, werden Sie dieses Problem vielleicht nie sehen (bis jemand optimiert oder Compiler oder Compiler-Versionen ändert). Wenn Sie einen Debugger verwenden, werden Sie dieses Problem möglicherweise nie bemerken (bis es zu spät ist).

Der Compiler sieht keinen Zusammenhang zwischen s und b, es handelt sich um zwei völlig verschiedene Elemente. Der Optimierer kann beschließen, nicht zu schreiben *s zurück in den Speicher, weil es sieht, dass *s hat eine Reihe von Operationen, so dass es diesen Wert in einem Register halten und erst am Ende (wenn überhaupt) im Speicher speichern kann.

Es gibt drei grundlegende Möglichkeiten, das oben beschriebene Zeigerproblem zu lösen:

  1. Erklären Sie s als flüchtig.
  2. Verwenden Sie eine Gewerkschaft.
  3. Verwenden Sie eine oder mehrere Funktionen, wenn Sie den Typ wechseln.

1 Stimmen

Der Char-Wert wird zuerst in einen Int umgewandelt (promoted), dann wird er verschoben. Wenn sowohl die linke als auch die rechte Seite ein Char ist, tritt dieses Problem auf.

0 Stimmen

"Die Operanden müssen vom Typ Integral oder Aufzählung sein und es werden ganzzahlige Promotionen durchgeführt. Der Typ des Ergebnisses ist der des promoteten linken Operanden. Das Verhalten ist undefiniert, wenn der rechte Operand negativ oder größer oder gleich der Länge in Bits des promoteten linken Operanden ist."

2voto

sep Punkte 3343

Sie sollten einen vorzeichenlosen char-Zeiger nicht in einen vorzeichenlosen short-Zeiger umwandeln (d.h. von einem Zeiger eines kleineren Datentyps in einen größeren Datentyp umwandeln). Dies liegt daran, dass davon ausgegangen wird, dass die Adresse korrekt ausgerichtet ist. Ein besserer Ansatz ist es, die Bytes in ein echtes vorzeichenloses Short-Objekt zu verschieben oder mit memcpy in ein vorzeichenloses Short-Array.

Zweifellos können Sie die Compiler-Einstellungen anpassen, um diese Einschränkung zu umgehen, aber das ist eine sehr subtile Sache, die in Zukunft kaputt gehen wird, wenn der Code weitergegeben und wiederverwendet wird.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X