Was ist falsch an diesem Casting in C-Code für AVR?

8

Ich habe zwei Variablen definiert:

uint8_t a[2];
uint16_t b;

Als nächstes möchte ich aals Variable vom Typ verwenden uint16_t, z

b = (uint16_t)a;

Das ist aber falsch! Meine Programme funktionieren mit solchem ​​Code nicht richtig. Alles ist in Ordnung , wenn ich ersetzen bzu uint8_t b[2]und die Nutzung element Operationen.

Warum?


quelle
5
Warum werfen Sie nicht einige Werte in Ihr Beispiel und sagen uns, was Sie von "richtig" erwarten, damit wir tatsächlich helfen können, ohne über Ihre semantische Absicht zu spekulieren.
Vicatcu
1
Dies wäre viel besser für Stack Overflow geeignet.
Scharfzahn

Antworten:

16

aist ein Zeiger auf ein Array von Bytes. Wenn Sie es in ein uint16_t umwandeln und es zuweisen b, benthält es die Adresse der Basis des Arrays (wo es gespeichert ist) im SRAM. Wenn Sie die beiden Bytes des Arrays aals Ganzzahl behandeln möchten , verwenden Sie eine Union, wie von user14284 vorgeschlagen. Beachten Sie jedoch, dass die Union das Byte-Array in der Reihenfolge der Speicherbytes der Architektur darstellt (in AVR wäre das wenig -endian, was bedeutet, dass Byte 0 das niedrigstwertige Byte ist). Der Weg, dies in Code zu schreiben, ist:

union{
  uint8_t a[2];
  uint16_t b;
} x;

x.b[0] = 0x35;
x.b[1] = 0x4A;

// by virtue of the above two assignments
x.a == 0x4A35 // is true

Eine andere Möglichkeit, dies ohne Verwendung einer Union zu tun, besteht darin, ain einen uint16_t-Zeiger umzuwandeln und ihn dann wie folgt zu dereferenzieren:

uint8_t a[2] = {0x35, 0x4A};
uint16_t b = *((uint16_t *) a);
b == 0x4A35; // because AVR is little endian

Wenn Sie den Puffer zum Speichern von Big-Endian-Daten verwenden (z. B. Netzwerkbyte-Reihenfolge), müssen Sie einen Byte-Swap durchführen, um eine dieser Techniken zu verwenden. Eine Möglichkeit, dies ohne Verzweigungen oder temporäre Variablen zu tun, ist:

uint8_t a[2] = {0x35, 0x4A};
a[0] ^= a[1];
a[1] ^= a[0];
a[0] ^= a[1];

a[0] == 0x4A; // true
a[1] == 0x35; // true

Dies ist übrigens kein AVR- oder sogar ein Embedded-Only-Problem. Application - Level - Netzwerk - Code für PCs geschrieben typischerweise Anrufe Funktionen genannt htonl, htons(Host - Netzwerk, 32- und 16-Bit - Varianten) und ntohl, ntohs(Netzwerk - Host, 32- und 16-Bit - Varianten) , deren Implementierungen sind Zielarchitektur abhängig, ob sie Tauschen Sie die Bytes aus oder nicht (unter der Annahme, dass die auf der Leitung übertragenen Bytes immer Big-Endian sind, wenn sie Teil von Mehrbyte-Wörtern sind).

vicatcu
quelle
Dies ist eine großartige Antwort. Der Schlüssel, der afür sich allein ist, ist ein Zeiger.
Jon L
2
"Eine andere Möglichkeit, dies ohne Verwendung einer Union zu tun, besteht darin, einen in einen uint16_t-Zeiger umzuwandeln und ihn dann zu dereferenzieren." - Tatsächlich verstößt diese Art des Castings häufig gegen strenge Aliasing-Regeln. Sie sollten es nicht tun, es sei denn, Sie kompilieren mit -fno-strict-aliasing.
Jim Paris
3

Wenn Sie die beiden 8-Bit-Variablen zu einer 16-Bit-Variablen verketten möchten, verwenden Sie a union. Wenn Sie ein einzelnes Mitglied von ain bumwandeln möchten, geben Sie an, welches Element des Arrays Sie verwenden möchten.

Joe Hass
quelle
2

In Ihrem Code setzen Sie nur den Zeiger auf das Array.

Sie müssen den Wert, auf den gezeigt wird, umwandeln a.

b = (uint16_t)*a;

Ich habe AVR nie verwendet, aber wenn Sie mit einer 16-Bit-Architektur arbeiten, müssen Sie sicherstellen, dass a wortausgerichtet ist. Andernfalls kann es zu einer Ausnahme kommen.

Bruno Ferreira
quelle
1
... das ist absolut nicht seine Absicht ... er möchte, dass b mit beiden Elementen von a in Beziehung steht (dies würde a [1] völlig ignorieren), außerdem gibt es keine Ausnahmen oder Einschränkungen für das, was Sie in avr-gcc wirken können
Vicatcu
0

Jedes Mitglied von a ist eine 8-Bit-Nummer. Es kann nichts Größeres halten. Das Casting auf 16 Bit hat nichts mit a zu tun . Es extrahiert lediglich den Wert, den a möglicherweise halten kann, und konvertiert ihn in 16 Bit, sodass er mit dem Format von b übereinstimmt, wenn der Wert dort gespeichert wird.

Sie haben sich nicht einmal auf ein Mitglied von a bezogen . Sie müssen eine [0] oder eine [1] verwenden (und keine [2]!). Wenn Sie eine für sich verwenden, erhalten Sie nur die Adresse. (Guter Fang, Bruno).

Wenn Sie a als Array aus zwei 8-Bit-Zahlen deklarieren, wird es auch nicht zu einer 16-Bit-Zahl. Sie können einige Dinge programmgesteuert ausführen, um 16-Bit-Werte mithilfe von 8-Bit-Sequenzen zu speichern und abzurufen, aber nicht so, wie Sie es sich vorgestellt haben.

gbarry
quelle
0

Wenn Sie die Bytes ain einen 16-Bit-Wert konvertieren möchten und die Darstellung Little-Endian ist (die unteren 8 Bits des Werts kommen im ersten Byte), tun Sie dies

uint16_t b = a[0] | (a[1] << 8);

Für eine Big-Endian-Darstellung tun

uint16_t b = (a[0] << 8) | a[1];

Vermeiden Sie dazu Zeiger-Casts oder Gewerkschaften, da dies zu Portabilitätsproblemen führt.

Sternenblau
quelle