Forum: Compiler & IDEs Endianess Funktion für ARM - möglicherweise Pointer-Problem


von Daniel G. (motello)


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

von Markus (Gast)


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.

von Daniel G. (motello)


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?!

von Markus (Gast)


Lesenswert?

Was gibt denn die Funktion zurück, wenn du 0xABCD eingibst?
Was passiert, wenn du b1 und b0 vertauschst?

von Daniel G. (motello)


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?

von Markus (Gast)


Lesenswert?

Könnte natürlich auch sein, dass var im Register übergeben wird. Dann 
ist die Adresse nicht definiert.

Gibt der Compiler keine Warnings?

von Markus (Gast)


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++);
    ...

von Daniel G. (motello)


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.

von Markus (Gast)


Lesenswert?

Ich finde es auch etwas seltsam. Um zu wissen, was der Compiler da 
wirklich macht, geh am besten mal den Assemblercode per Hand durch.

von Daniel G. (motello)


Lesenswert?

ja, mir wird wohl nichts anderes übrig bleiben. ich werde dann 
berichten!

danke und gruß
daniel

von Daniel G. (motello)


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

von Marcus H. (mharnisch) Benutzerseite


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/

von Juergen (Gast)


Lesenswert?

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.

von Marcus H. (mharnisch) Benutzerseite


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/

von Daniel G. (motello)


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

von Daniel G. (motello)


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.

von Marcus H. (mharnisch) Benutzerseite


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/

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.