Endianess Funktion für ARM - möglicherweise Pointer-Problem

OP #1231596
Lesenswert?

Hallo,

ich habe eine Frage zu einer Funktion zur Endianess Umwandlung:
1
unsigned short endians (unsigned short var) {
2
    unsigned char *ptr = (unsigned char*) &var;
3
    unsigned short b0 = (unsigned short) *(ptr++);
4
    unsigned short b1 = (unsigned short) *(ptr++);
5
    return b1 | (b0 << 8 );
6
}

Diese funktion soll die beiden Bytes tauschen, es kommt aber nicht das 
richtige Ergebnis raus. Kompiliert wurde mit GCC als cross-compiler für 
arm926ej-s.

Ich habe es dann anders gelöst:
1
unsigned short endians (unsigned short var) {
2
    unsigned short b0 = var & 0xff00;
3
    unsigned short b1 = var & 0x00ff;
4
    return ((b0 >> 8 ) | (b1 << 8 ));
5
}

Aber ich muss trotzdem wissen warum die erste Variante nicht 
funktionierte, da ich weitere Probleme mit meinem Programm habe. Ich 
hoffe jemand hat einen Tipp für mich.

Viele Grüße
Daniel
Gast #1231626
Lesenswert?

Falls ein short aus zwei Bytes besteht, kann das entweder so
    | HI | LO |
oder so
    | LO | HI |
im Speicher abgelegt sein.

Das Konstrukt
    unsigned char *ptr = (unsigned char*) &var;
zeigt dann auf das Byte mit der niedrigeren Adresse.

Je nach Endianess ist das dann entweder das HI oder das LO Byte. Wenn es 
das HI Byte ist, dann stellt dein Code genau das Original wieder her, 
vertauscht also nix.
OP #1231660
Lesenswert?

Also der uC arbeitet in little Endian, also:

1. var = | LO | HI |
2. ptr zeigt auf LO
3. LO wird in b0 gespeichert
4. HI wird in b1 gespeichert
5. b0 erfährt linksshift um 8
6. b0 enthält LO im höherwertigen byte, b1 enthält HI im niederwertige 
byte
nach addition im Speicher also: | b1 | b0 |
und damit: | HI | LO |

das ist doch richtig, oder nicht?!
OP #1231714
Lesenswert?

Ich habe die Funktion so getestet:

unsigned int x = 0xabcd;

if (endians(x) == x)
    return error;

if (endians(endians(X)) != x)
    return error;

es wird weder die gleiche zahl noch die korrekt umgestellte 
zurückgegeben. ich versteh das nicht... hab ich C etwa nicht verstanden 
oder macht der compiler merkwürdige sachen?
Gast #1231737
Lesenswert?

Testen müsste sich das so lassen:
    unsigned short endians (unsigned short var) {
        unsigned short tmp = var;
        unsigned char *ptr = (unsigned char*) &tmp;
        unsigned short b0 = (unsigned short) *(ptr++);
    ...
OP #1231834
Lesenswert?

nein, keine warnings trotz -Wall. So weit ich weiss wird var im register 
übergeben so wie generell bis zu 4 variablen. ich habe auch versucht var 
vorher in einer lokalen variablen zu speichern aber kein erfolg. 
außerdem müsste der compiler, wenn es mit dem register nicht geht, doch 
von selbst auf eine lösung kommen, oder?

meinst du meine testroutine ist nicht korrekt? mit der neuen variante 
wird kein fehler gemeldet.
OP #1242924
Lesenswert?

Ich habe dieses und andere Probleme hinsichtlich fehlerhaftem Verhalten 
des Programms gelöst: Mein Fehler war es, Pointer-Casts zu benutzen und 
diese gecasteten Pointer dann auch noch zu dereferenzieren.

Laut ISO-C dürfen Pointer auf nicht-kompatible Datentypen nicht auf die 
gleiche Adresse zeigen (aliasing).
1
int i = 5;
2
char wert = *( (char*) &i);

...geht also nicht. Zumindest nicht bei den heutigen gcc versionen, bei 
denen aliasing durch optimierungsroutinen nicht möglich ist.

Die Lösung ist, wenn man beispielsweise ein struct aus verschiedenen 
Datentypen auch durch ein char-array lesen oder beschreiben will, ein 
union:
1
union {
2
    struct {
3
        int i;
4
        int a;
5
        short c;
6
    } __attribute__ ((packed)) fields;
7
    char bytes[10];
8
} __attribute__ ((packed)) header;

Was außerdem Probleme machen kann: in gcc müssen alle Datentypen größer 
1 Byte aligned sein. Liegt ein Integer also irgendwo unaligned im 
speicher wird es nicht lesbar sein. dies kommt z.B. bei Netzwerkpaketen 
vor, die durch den den MAC im Speicher abgelegt werden. Da muss man eben 
char nutzen und den header in ein struct in einer union kopieren.

Quelle: http://mail-index.netbsd.org/tech-kern/2003/08/11/0001.html
und: http://gcc.gnu.org/bugs.html#known
Persönliche Seite #1243100
Lesenswert?

Daniel G. wrote:
> Liegt ein Integer also irgendwo unaligned im speicher wird es nicht
> lesbar sein. dies kommt z.B. bei Netzwerkpaketen vor, die durch den
> den MAC im Speicher abgelegt werden. Da muss man eben char nutzen
> und den header in ein struct in einer union kopieren.

Das ist ein immer wieder gern hervorgeholtes Gerücht. Das
1
__attribute__((packed))

hat genau den Zweck, dem Compiler zu sagen, dass die Daten
möglicherweise nicht an Wort/Halbwort-Grenzen liegen und er daher
geeigneten Code erzeugen muss, um die Daten stückweise
zusammenzuklauben. Also im Prinzip genau das, was Du mit dem
Byte-Array zu Fuß machst.

Wir haben in diesem Forum bereits festgestellt, dass einige GCC
Versionen dabei Fehler machen. Erfahrungsgemäß ist man mit dem GCC von
CodeSourcery ganz gut bedient.

Gruß
Marcus
http://www.doulos.com/arm/
Persönliche Seite #1243165
Lesenswert?

Juergen wrote:
> Manch ein ARM schreibt gern mal auf die alignte Adresse, auch wenn du
> ihm eine nicht alignte gibst. Und das ohne jegliche Fehlermeldung ...
>
> D.h. z.B. nach abc0 statt nach abc1.

Das Verhalten ist im Architecture Reference Manual für Prozessoren ohne 
System Control Coprocessor genau so spezifiziert.

Daher muss der Compiler, der prinzipiell die Aufgabe hat, korrekten Code 
für die Zielplattform zu erzeugen, derartige Speicherzugriffe vermeiden 
und ggf durch Zugriff auf einzelne Bytes nachbilden.

Gruß
Marcus
http://www.doulos.com/arm/
OP #1243193
Lesenswert?

Hallo Marcus,

wenn ein Datentyp kleiner einem Wort gelesen werden soll, so 
beispielsweise in meinem struct für Netzwerk-Header die durch 
das((packed)) eben nicht alle aligned im speicher liegen, wird der 
kompiler natürlich darauf zugreifen können.

Ich meinte aber folgendes: Wenn Pakete empfangen werden und in den 
Speicher gelegt werden und daraus Datentypen größer einem Byte gelesen 
werden sollen (die je nicht alle aligned sind), kann kein direkter 
zugriff erfolgen. So schreibt es jedenfalls die Autorin des verlinkten 
Textes oder zumindest habe ich es so verstanden.

Wobei genau machen manchen versionen von gcc Fehler? bei dem beispiel 
was sich mit der union gegeben habe oder wobei sonst?

Gruß
Daniel
OP #1243199
Lesenswert?

Achso, _attribute_ ((packed)) ist übrigens dazu da um die Datentypen 
ohne zwischenraum im Speicher anzuordnen. Der Speicherraum käme 
normalerweise zustande, weil der Compiler die Variablen so anordnet 
damit er schnell drauf zugreifen kann, halt aligned.

Durch das Packed ergibt sich zwangsweise, dass der compiler den Zugriff 
auf kleinere Datentypen als 32Bit (bei ARM9) emulieren muss.
Persönliche Seite #1243210
Lesenswert?

Daniel G. wrote:
> Ich meinte aber folgendes: Wenn Pakete empfangen werden und in den
> Speicher gelegt werden und daraus Datentypen größer einem Byte gelesen
> werden sollen (die je nicht alle aligned sind), kann kein direkter
> zugriff erfolgen.

Doch, wenn Du dem Compiler sagst, dass genau das der Fall sein
kann. Der Compiler wird dadurch angehalten, anstelle eines
Wortzugriffs (LDR) eine Reihe von Einzelzugriffen (LDRH, LDRB)
durchzuführen. Nicht sonderlich effizient, funktioniert aber.

> Wobei genau machen manchen versionen von gcc Fehler? bei dem beispiel
> was sich mit der union gegeben habe oder wobei sonst?

Der betroffene GCC greift mit einem Wortzugriff auf ein Wort zu, das
nicht zwingend an einer durch vier teilbaren Adresse liegt -- obwohl
wir den Compiler mit dem Attribut "packed" darauf hingewiesen haben.

Das hier ist ein Beitrag in einem recht langen Thread
Beitrag "Re: Kann ich garantieren, dass ein C Struct genau 3 Byte im Speicher belegt?"

Gruß
Marcus
http://www.doulos.com/arm/

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren