Forum: Compiler & IDEs Pointer auf ungerade Adresse


von bierbauch (Gast)


Lesenswert?

Hallo zusammen,

ich habe ein Problem, dass ich anhand eines Beispiels erläutern möchte
(Hinweis vorab: Ich arbeite mit einem XScale - Prozessor (PXA255) und 
gcc 4.1.2):

#include <stdio.h>

[...]

typedef struct
{
  Enum8         bla;
  DInt          diValue;
  Real          rValue;
} _attribute_ ((packed)) Test_Struct;
//} Test_Struct;

Test_Struct TestVar[3];



int main ()
{
 Real *rp;

 rp = &TestVar[1].rValue;
 printf ("rValue = %f\n", TestVar[1].rValue);
 printf ("diValue = %ld\n", TestVar[1].diValue);
 printf("fp_Adresse: %p\n",rp);
 *f_p = 5.678;
 printf ("rValue = %f\n", TestVar[1].rValue);
 printf ("diValue = %ld\n", TestVar[1].diValue);


 return (1);

}


Ein float-Pointer wird angelegt und zeigt auf ein Strukturelement 
(rValue). Nun möchte ich per Zuweisung in den Speicher, auf den f_p 
zeigt den Wert 5,678 reinschreiben. Das geht aber nur dann gut, wenn die 
oben deklarierte Struktur nicht mit _attribute_ ((packed)) angelegt 
wird. Klar ist, dass f_p auf eine ungerade Adresse zeigt. Bei der 
Zuweisung wird in den Speicherbereich vom diValue reingeschrieben (weil 
hier die nächsthöhere gerade Adresse ist). Meine Fragen:
1. Ist das ein Prozessor- oder ein Compilerproblem?
2. Wie kann ich das Problem umgehen bzw. beheben?

Ich habe hier im Forum schon gesucht, die Antworten, die jeweils gegeben 
wurden haben mir nicht wirklich weiter geholfen.

Danke schonmal & Gruß, Bierbauch

von Rolf Magnus (Gast)


Lesenswert?

> 1. Ist das ein Prozessor- oder ein Compilerproblem?

Es ist eher ein Problem deines Programms bzw. deiner Erwartung, daß der 
Prozessor mit Daten umgehen kann, die mit falschem Alignment im Speicher 
stehen. Manche Prozessoren unterstützen das bei reduzierter Perfomance, 
andere gar nicht. Generell sollte man es vermeiden.

> 2. Wie kann ich das Problem umgehen bzw. beheben?

Dem Compiler das Alignment überlassen, indem du das Attribut einfach 
wegläßt.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

bierbauch wrote:
> typedef struct
> {
>   Enum8         bla;
>   DInt          diValue;
>   Real          rValue;
> } _attribute_ ((packed)) Test_Struct;
> //} Test_Struct;
>
> Test_Struct TestVar[3];
>  rp = &TestVar[1].rValue;

> 1. Ist das ein Prozessor- oder ein Compilerproblem?

Üblicherweise macht sich ein Compiler nicht den Wolf, unalignten Zugriff 
zu unterstützen, wenn die zugrundeliegende Architektur das nicht kann.

> 2. Wie kann ich das Problem umgehen bzw. beheben?

Falls du nicht umcodieren kannst zu vernünftigen Alignments, kann über 
eine Union gelesen werden:
1
union
2
{
3
   unsigned char asByte[4];
4
   float asFloat;
5
} foo;

Aber Vorsich:
-- Falls der µC spezielle FP-regs hat, kann das die Darstellung 
korrumpieren (automatische Konvertierung/Rundung durch die Hardware, 
wenn ein Wert in ein fp-reg geschoben wird).
-- strict aliasing beachten

Johann

von Ingo E. (ogni42)


Lesenswert?

Versuch mal, den Float an den Anfang der Struktur zu legen. Der Enum 
scheint ja das Misalignment reinzubringen. Aber Vorsicht: Das ist ein 
Hack, der nur dann funktionieren kann, wenn die Struktur als Ganzes 
wieder aligned ist.

Generell gilt, was Rolf Magnus gesagt hat: Wenn es die CPU nicht 
unterstützt, dann lass' es weg. Durch eine entsprechende Sortierung 
kannst Du zumindest die Padding bytes umgehen. Ob das vom Design der 
Struktur dann noch Sinn macht, musst Du selbst entscheiden.

Ich würde immer ein gutes Design favorisieren.

von Marcus H. (mharnisch) Benutzerseite


Lesenswert?

Rolf Magnus wrote:
>> 1. Ist das ein Prozessor- oder ein Compilerproblem?
>
> Es ist eher ein Problem deines Programms bzw. deiner Erwartung, daß der
> Prozessor mit Daten umgehen kann, die mit falschem Alignment im Speicher
> stehen.

Johann L. wrote:
> Üblicherweise macht sich ein Compiler nicht den Wolf, unalignten Zugriff
> zu unterstützen, wenn die zugrundeliegende Architektur das nicht kann.

Mit Verlaub, das ist Quatsch. Wenn mir ein Compiler die Möglichkeit 
gibt, Daten explizit als unaligned zu deklarieren, dann erwarte ich 
entweder

1. dass der Compiler das unabhängig von der Zielarchitektur auch
   hinbekommt. (bevorzugtes Verhalten)

2. dass der Compiler mir eine Fehlermeldung ausgibt, falls die
   Zielarchitektur unaligned Daten nicht unterstützt und der Compiler
   sich ebenfalls nicht darum kümmern möchte.

Jedes andere Verhalten ist ein Bug des Compilers. Wir hatten hier vor 
nicht allzulanger Zeit einen ähnlichen Thread, in dem wir das alles 
schon Mal durchgekaut haben.

Mein Tip: Versuch's mal mit dem GCC für ARM von CodeSourcery.

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

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Marcus Harnisch wrote:
> Rolf Magnus wrote:
>>> 1. Ist das ein Prozessor- oder ein Compilerproblem?
>>
>> Es ist eher ein Problem deines Programms bzw. deiner Erwartung, daß der
>> Prozessor mit Daten umgehen kann, die mit falschem Alignment im Speicher
>> stehen.
>
> Johann L. wrote:
>> Üblicherweise macht sich ein Compiler nicht den Wolf, unalignten Zugriff
>> zu unterstützen, wenn die zugrundeliegende Architektur das nicht kann.
>
> Mit Verlaub, das ist Quatsch. Wenn mir ein Compiler die Möglichkeit
> gibt, Daten explizit als unaligned zu deklarieren, dann erwarte ich
> entweder
>
> 1. dass der Compiler das unabhängig von der Zielarchitektur auch
>    hinbekommt. (bevorzugtes Verhalten)
>
> 2. dass der Compiler mir eine Fehlermeldung ausgibt, falls die
>    Zielarchitektur unaligned Daten nicht unterstützt und der Compiler
>    sich ebenfalls nicht darum kümmern möchte.
>
> Jedes andere Verhalten ist ein Bug des Compilers. Wir hatten hier vor
> nicht allzulanger Zeit einen ähnlichen Thread, in dem wir das alles
> schon Mal durchgekaut haben.

Nehmen wir mal an, du hast einen long * p, also einen Zeiger auf einen 
32-Bit-Wert. Nehmen wir weiterhin an, daß die Architektur nur alignten 
Zugriff unterstützt. Beachte, daß in long keine Information über das 
Aligment transportier wird (packed ist ein Attribut, kein Qualifier!), 
d.h. man müsste Code erzeugen, der zur Laufzeit unterscheidet, ob p 
aligned ist oder nicht (Oder immer Code erzeugen, der unalignten Zugriff 
unterstützt). Oder seh ich das falsch? Und zwar für jeden solchen 
Zugriff. Wenn der Zugriff über ein Symbol erfolgt ist klar, wie es zu 
geschehen hat (sagt die ABI oder EABI), weil da Attribute dran kleben. 
Wie soll ein Compiler jedoch long a = *(long*) b); implementieren?

So, daß es für jeden unalignten Pointer geht, und alle Anwender auf dem 
Matte stehen, und über den langsamen und breiten und nicht-atomaren Code 
jammern?
Oder das, was die Architektur unterstützt. Zudem ist sowas wie gesagt in 
einer ABI oder EABI beschrieben (oder sollte es zumindest), die mir für 
diese Architektur zugegebenermassen nicht vertraut ist, und sich von 
Compiler zu Compiler unterscheiden kann -- auch zwischen 
unterschiedlichen gcc-Ports für die gleiche Architektur.

Johann

von Marcus H. (mharnisch) Benutzerseite


Lesenswert?

Johann L. wrote:
> Nehmen wir mal an, du hast einen long * p, also einen Zeiger auf einen
> 32-Bit-Wert. Nehmen wir weiterhin an, daß die Architektur nur alignten
> Zugriff unterstützt. Beachte, daß in long keine Information über das
> Aligment transportier wird (packed ist ein Attribut, kein
> Qualifier!),

In "long" nicht, aber in der Typdeklaration von Test_Struct.

> d.h. man müsste Code erzeugen, der zur Laufzeit unterscheidet, ob p
> aligned ist oder nicht (Oder immer Code erzeugen, der unalignten Zugriff
> unterstützt).

Das müsste man nur, wenn der Programmierer absichtlich (type casts,
siehe unten) versucht, den Compiler daran zu hindern, das alignment
anhand der Deklarationen zu erkennen.

> Wie soll ein Compiler jedoch long a = *(long*) b); implementieren?

Wie gesagt, selbst schuld. Ein
1
typedef long p_long __attribute__((packed));
2
3
p_long a = *(p_long *)b;

hätte vermutlich schon geholfen.

> So, daß es für jeden unalignten Pointer geht, und alle Anwender auf dem
> Matte stehen, und über den langsamen und breiten und nicht-atomaren Code
> jammern?

Man muss sich natürlich über die Auswirkungen der Verwendung solcher
Konstrukte bewusst sein. Aber die von mir zitierten Aussagen sind
schlichtweg falsch.

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

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Marcus Harnisch wrote:
> Johann L. wrote:
>> Nehmen wir mal an, du hast einen long * p, also einen Zeiger auf einen
>> 32-Bit-Wert. Nehmen wir weiterhin an, daß die Architektur nur alignten
>> Zugriff unterstützt. Beachte, daß in long keine Information über das
>> Aligment transportier wird (packed ist ein Attribut, kein
>> Qualifier!),
>
> In "long" nicht, aber in der Typdeklaration von Test_Struct.

Welchen Typs ist & Test_Struct.rValue ?

> Das müsste man nur, wenn der Programmierer absichtlich (type casts,
> siehe unten) versucht, den Compiler daran zu hindern, das alignment
> anhand der Deklarationen zu erkennen.
>
>> Wie soll ein Compiler jedoch long a = *(long*) b); implementieren?
>
> Wie gesagt, selbst schuld. Ein
Nein, damit wollte ich nich ein Cast-Hacking ausdrücken, sondern 
Zugriffe wie in
1
long foo (long * b)
2
{
3
    return a;
4
}

>
1
> typedef long p_long __attribute__((packed));
2
> 
3
> p_long a = *(p_long *)b;
4
>

Ein gepackter long? Mal vorausgesetzt, daß men dem ne sinnvolle Semantik 
zuordnet (zB daß ein unalignter Zugriff erfolgen soll): Was ist dann der 
Unterschied zwischen
1
long ** __attribute__((packed)) p;
2
long * __attribute__((packed)) * p;
3
long __attribute__((packed)) * * p;
4
long __attribute__((packed)) * __attribute__((packed)) * p;
Attribute sind keine Qualifier, d.h. sowas ist nicht ausdrückbar und es 
ist nicht konsistent unterstützbar. Jedenfalls nicht so wie andere 
Qualifier die Zugriffe regeln wie etwa volatile. Wenn du das Attribut 
durch einen Qualifier ersetzt, ergeben alle diese Definitionen 
unterschiedliche Typen (zumindest im gcc). Für Attribute ist das nicht 
der Fall.

Ab irgendeiner Ebene fliegt's dir also um die Ohren, da wäre genau so, 
als könnte man nicht unterscheiden zwischen
1
int * volatile p; // und
2
int volatile * p;

Johann

von Marcus H. (mharnisch) Benutzerseite


Lesenswert?

Interessant... Aber wir schweifen ab. Mein Einwand bezog sich lediglich 
auf die Aussage, dass Compiler unaligned Zugriffe auf bestimmten 
Architekturen (wie, z.B. ARM <v6) nicht richtig unterstützen. Sie tun es 
(abgesehen von einigen GCC bugs, siehe anderer Thread), aber hellsehen 
kann der Compiler natürlich nicht.

Johann L. wrote:
> Ein gepackter long? Mal vorausgesetzt, daß men dem ne sinnvolle Semantik
> zuordnet

DU wolltest ihn doch packen :-)

> long ** __attribute__((packed)) p;
> long * __attribute__((packed)) * p;
> long __attribute__((packed))   p;
> long __attribute__((packed)) * __attribute__((packed)) * p;

> Attribute sind keine Qualifier, d.h. sowas ist nicht ausdrückbar und es
> ist nicht konsistent unterstützbar.

Habe leider gerade keine Zeit, das nachzuprüfen. Nebenbei, im RealView 
Compiler gibt es den (ebenfalls nicht standard konformen) Qualifier 
__packed.

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

von Hans-jürgen H. (hjherbert) Benutzerseite


Lesenswert?

Das attribute ((packed)) ist für die Fälle da, wo man Binärdateien von 
einem anderen System weiterverarbeiten muss.

Zum Weiterverarbeiten muss dann jedes Element in eine "normale" Struktur 
kopieren. Das darf dann auch keine Zuweisung sein, sondern memcpy.

von Marcus H. (mharnisch) Benutzerseite


Lesenswert?

Hans-jürgen Herbert wrote:
> Zum Weiterverarbeiten muss dann jedes Element in eine "normale" Struktur
> kopieren.

Nein, man "muss" nicht. Kann sich je nach Anwendung aber lohnen. Die 
Zugriffe auf unaligned data sind bei ARM Architekturen <6 nicht optimal. 
Selbst bei den neueren Prozessoren hat man die zusätzlichen Buszyklen. 
Letzteres ist aber nicht ARM-spezifisch.

Bei häufigem Zugriff auf die Daten kann die Ersparnis den Aufwand für 
das Kopieren der Daten aufwiegen.

> Das darf dann auch keine Zuweisung sein, sondern memcpy.

Das darf selbstverständlich auch eine Zuweisung sein.

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

von Rolf Magnus (Gast)


Lesenswert?

> Die Zugriffe auf unaligned data sind bei ARM Architekturen <6 nicht
> optimal.

Und bei größeren dann gar nicht möglich - zumindest bei dem ARM7, mit 
dem ich arbeite.

> Selbst bei den neueren Prozessoren hat man die zusätzlichen
> Buszyklen.

Nach meiner Erfahrung ist die Zahl der Prozessoren, bei denen das so 
ist, geringer, als die, bei denen es gar nicht geht.

> Das darf dann auch keine Zuweisung sein, sondern memcpy.
>
> Das darf selbstverständlich auch eine Zuweisung sein.

Ist halt genauso wie bei jedem anderen Zugriff. Wenn das bei Daten mit 
falschem Alignment nicht geht, geht auch die Zuweisung nicht.

von Marcus H. (mharnisch) Benutzerseite


Lesenswert?

Rolf Magnus wrote:
>> Die Zugriffe auf unaligned data sind bei ARM Architekturen <6 nicht
>> optimal.
>
> Und bei größeren dann gar nicht möglich - zumindest bei dem ARM7, mit
> dem ich arbeite.

Aber ein ARM7 (bzw. ARM7TDMI) implementiert die Architektur 7T, ist
also <6. Zugegebenermaßen etwas verwirrend. Seit ARM11 (6) wird
unaligned Zugriff durch die Hardware unterstützt.

>> Selbst bei den neueren Prozessoren hat man die zusätzlichen
>> Buszyklen.
>
> Nach meiner Erfahrung ist die Zahl der Prozessoren, bei denen das so
> ist, geringer, als die, bei denen es gar nicht geht.

Wenn ich beispielsweise einen 32bit Datenbus habe, und mein 32bit Wert
an Adresse 1 beginnt, dann benötige ich wenigstens zwei Buszyklen,
oder nicht?

> Ist halt genauso wie bei jedem anderen Zugriff. Wenn das bei Daten mit
> falschem Alignment nicht geht, geht auch die Zuweisung nicht.

So formuliert hast Du natürlich recht. Aber das Alignment ist ja nicht
in dem Sinne falsch. Es entspricht nur nicht dem Alignment der
Datenzugriffe des Prozessors. Jetzt ist es am Compiler, sich die Daten
durch Byte/Halbwort Zugriffe zusammenzubasteln.

Ich habe mal versucht, das mit ähnlichem Code wie dem des OP
darzustellen. Das Programm läuft wie erwartet und die Ausgabe ist in
beiden Fällen identisch.

Eine Struktur ist aligned, die andere nicht. Die zweite (unaligned)
wird initialisiert und die Elemente werden durch Zuweisung in die
andere Struktur (aligned) kopiert.

Gruß
Marcus
http://www.doulos.com/arm/
1
#include <stdio.h>
2
3
typedef struct
4
{
5
  unsigned char  bla;
6
  unsigned int   diValue;
7
  float          rValue;
8
} __attribute__ ((packed)) Test_Struct_p;
9
10
typedef struct
11
{
12
  unsigned char  bla;
13
  unsigned int   diValue;
14
  float          rValue;
15
} Test_Struct;
16
17
18
Test_Struct   TestVar[3];
19
Test_Struct_p TestVar_p[3];
20
21
int main ()
22
{
23
    int i;
24
25
    TestVar_p[0].rValue = 5.678;
26
27
    printf ("rValue = %f\n", TestVar_p[0].rValue);
28
29
    for (i = 2; i>=0; i--)
30
    {
31
        TestVar[i].bla     = TestVar_p[i].bla;
32
        TestVar[i].diValue = TestVar_p[i].diValue;
33
        TestVar[i].rValue  = TestVar_p[i].rValue;
34
    }
35
36
    printf ("rValue = %f\n", TestVar[0].rValue);
37
38
39
 return (1);
40
41
}

von Rolf Magnus (Gast)


Lesenswert?

> Aber ein ARM7 (bzw. ARM7TDMI) implementiert die Architektur 7T, ist
> also <6.

Ach so.

> Wenn ich beispielsweise einen 32bit Datenbus habe, und mein 32bit
> Wert an Adresse 1 beginnt, dann benötige ich wenigstens zwei
> Buszyklen, oder nicht?

Das nehme ich an.

> So formuliert hast Du natürlich recht. Aber das Alignment ist ja nicht
> in dem Sinne falsch. Es entspricht nur nicht dem Alignment der
> Datenzugriffe des Prozessors.

Das ist Ansichtssache.

> Jetzt ist es am Compiler, sich die Daten durch Byte/Halbwort Zugriffe
> zusammenzubasteln.

Nein, denn der Compiler selbst kann nur in Spezialfällen wissen, ob das 
Alingment paßt. Damit das allgemein funktioniert, müßte er für so 
ziemlich jeden Zugriff Code einbauen, der das zur Laufzeit macht. Das 
würde einen ziemlichen Overhead bezüglich Codegröße und Laufzeit 
bedeuten, auch für Zugriffe mit passendem Alignment.

> Eine Struktur ist aligned, die andere nicht. Die zweite (unaligned)
> wird initialisiert und die Elemente werden durch Zuweisung in die
> andere Struktur (aligned) kopiert.

Ja, wenn der Prozessor das unterstützt, geht das. Wenn der Code eh nicht 
portabel sein soll, kann man das wohl so machen. Allgemein würde ich 
trotzdem lieber darauf verzichten.

von Marcus H. (mharnisch) Benutzerseite


Lesenswert?

Rolf Magnus wrote:
> Marcus Harnisch wrote:
>> Aber ein ARM7 (bzw. ARM7TDMI) implementiert die Architektur 7T, ist
>> also <6.

Au weia. Das war natürlich der ungünstigste Ort für einen
Tippfehler. Der Satz sollte lauten:

>> Aber ein ARM7 (bzw. ARM7TDMI) implementiert die Architektur 4T, ist
>> also <6.


>> Jetzt ist es am Compiler, sich die Daten durch Byte/Halbwort Zugriffe
>> zusammenzubasteln.
>
> Nein, denn der Compiler selbst kann nur in Spezialfällen wissen, ob das
> Alingment paßt.

Wie z.B. duch Angabe von
1
__attribute__ ((packed))

> Damit das allgemein funktioniert, müßte er für so ziemlich jeden
> Zugriff Code einbauen, der das zur Laufzeit macht. Das würde einen
> ziemlichen Overhead bezüglich Codegröße und Laufzeit bedeuten, auch
> für Zugriffe mit passendem Alignment.

Genau. Man würde die mehrfach benötigten Teile der Struktur in lokale
Variablen kopieren, und/oder darauf vertrauen, dass der Compiler die
Registerverwaltung beherrscht. Bei CPUs mit relativ vielen Registern
wird ja der Zugriff auf die Struktur nicht jedes Mal neu ausgeführt.

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.