Forum: Compiler & IDEs union mit Struktur und Array gleicher Größe?


von Holm T. (Gast)


Lesenswert?

Ich möchte gerne eine Struktur mit unterschiedlichen Integer variablen 
unterschiedlicher Größe byteweise auslesen/schreiben. es geht hier ganz 
einfach um EEPROM Inhalte eines AVR, der EEPROM-Inhalt soll extern 
zusammengestellt werden, die Definition der Struktur befindet sich in 
einem Headerfile das von der AVR Firmware eingebunden wird.
Eine Struktur deshalb, weil nur die Reihenfolge der Variablen nur 
innerhalb einer Solchen garantiert ist (Gegenteil von 
-no-toplevel-reorder)

Also z.B.
struct eeprom_val
    {
      uint8_t bla;
      uint16_t fasel;
      uint32_t foo;
      int16_t bar;
    }ee-val;


Wie zur Hölle vereinbare ich nun eine union die die Struktur und ein 
Array gleicher Länge enthält, die mir Zugriff auf den Speicherbereich er 
Struktur ermöglicht?

Gruß,

Holm

von Dumpfbacke (Gast)


Lesenswert?

Du brauchst eine Typdefinition für deine Struktur, diesen
Typen baust du dann in deine Union ein.

von Ralf G. (ralg)


Lesenswert?

1
union
2
{
3
  struct
4
  {
5
    uint8_t bla;
6
    uint16_t fasel;
7
    uint32_t foo;
8
    int16_t bar;
9
  } ee_val;
10
  uint8_t array[9];
11
} ee_union;

Das müsste schon reichen.

von Dirk B. (dirkb2)


Lesenswert?

Bei der 9 wäre ich mir nicht ganz so sicher.

Der Compiler darf zwischen den struct-Membern Padding-Bytes einfügen.

(Das kann man mit Compilerdirektiven verhindern)

von Nop (Gast)


Lesenswert?

Dirk B. schrieb:

> Der Compiler darf zwischen den struct-Membern Padding-Bytes einfügen.

Aber auf einem 8-bitter (AVR) gibt's dazu doch keinen Grund?

von Ralf G. (ralg)


Lesenswert?

Dirk B. schrieb:
> Bei der 9 wäre ich mir nicht ganz so sicher.
> ...Padding-Bytes...
Ich glaube, der Compiler darf sogar die Reihenfolge ändern.
Die Konstruktion sollte man sowieso unter besondere Beobachtung stellen.

Etwas universeller:
1
#define EE_SIZE  sizeof(struct ee_struct)
2
struct ee_struct
3
{
4
  uint8_t bla;
5
  uint16_t fasel;
6
  uint32_t foo;
7
  int16_t bar;
8
};
9
union ee_union
10
{
11
  struct ee_struct ee_val;
12
  uint8_t ee_array[EE_SIZE];
13
};

von Holm T. (Gast)


Lesenswert?

Nop schrieb:
> Dirk B. schrieb:
>
>> Der Compiler darf zwischen den struct-Membern Padding-Bytes einfügen.
>
> Aber auf einem 8-bitter (AVR) gibt's dazu doch keinen Grund?

Mein Haken daran ist, das ich die Daten auf einer 64Bit Maschine 
erzeugen will und im AVR dann lesen. Das ist hier also durchaus zu 
beachten ..und zu unterbinden.

Gruß,

Holm

von Nop (Gast)


Lesenswert?

Holm T. schrieb:

> Mein Haken daran ist, das ich die Daten auf einer 64Bit Maschine
> erzeugen will und im AVR dann lesen.

In dem Fall empfehle ich allein schon wegen Endianess eine 
Serialisierung, daß Du also wirklich byteweise in das Array schreibst.

von Nop (Gast)


Lesenswert?

Ach ja, und auf dem AVR dann entweder die passende Deserialisierung, 
also byteweise lesen und shiften, oder falls das nicht möglich ist, 
sondern direkt aus dem Flash gelesen werden muß, daß Du auf 64bit die 
Bytes byteweise so in das Array schiebst, daß die Endianess auf AVR 
schon mit berücksichtigt wird.

Das Lesen auf AVR wäre dann nur noch Dereferenzieren des 
struct-Pointers. Padding sollte es auf AVR ja nicht geben.

von Holm T. (Gast)


Lesenswert?

Ralf G. schrieb:
> Dirk B. schrieb:
>> Bei der 9 wäre ich mir nicht ganz so sicher.
>> ...Padding-Bytes...
> Ich glaube, der Compiler darf sogar die Reihenfolge ändern.
> Die Konstruktion sollte man sowieso unter besondere Beobachtung stellen.
>
> Etwas universeller:
>
1
> #define EE_SIZE  sizeof(struct ee_struct)
2
> struct ee_struct
3
> {
4
>   uint8_t bla;
5
>   uint16_t fasel;
6
>   uint32_t foo;
7
>   int16_t bar;
8
> };
9
> union ee_union
10
> {
11
>   struct ee_struct ee_val;
12
>   uint8_t ee_array[EE_SIZE];
13
> };
14
>

ja..besondere Bobachtung ... :-)

Ich habe früher mal die Variablen einfach nacheinander deklariert und 
das ging jahrelang gut. Ab gcc4.8 habe ich dann groß Augen bekommen, 
weil die plötzlich in umgekehrter Reihenfolge im EEprom standen, 
vertreibe ließ sich das mit "-f-no-toplevel-reorder". Jörg sagte mir 
damals das die Reihenfolge nur in einer Struktur nicht eränder wird, das 
ich nun mit maschinenabhängigem Padding zu kämpfen habe ist ein anderes 
Paar Schuhe.

Das Konstrukt oben ging IMHO auch schief, mir fällt jetzt dazu die 
Fehlermeldung nicht mehr ein (habe das vorige Woche probiert).

Ich probiere mal..
Gruß,

Holm

von Dr. Sommer (Gast)


Lesenswert?

Holm T. schrieb:
> Jörg sagte mir
> damals das die Reihenfolge nur in einer Struktur nicht eränder wird, das
> ich nun mit maschinenabhängigem Padding zu kämpfen habe ist ein anderes
> Paar Schuhe.

Hinzu kommt auch noch die Byte-Reihenfolge. unions sind nunmal 
grundsätzlich nicht zur Konvertierung von Daten da, sondern nur zum 
Speicher sparen. Einfach Pointer umcasten ist genauso verkehrt. Die 
einzig korrekte (mit wohldefiniertem Ergebnis) Vorgehensweise ist es, 
die Bytes einzeln mittels Bitshifts in/aus Arrays zu packen. Das ist in 
C leider recht umständlich (weswegen dann doch oft die union-Variante 
genommen wird), funktioniert dafür aber garantiert immer auf allen 
Plattformen mit allen Compilern/Versionen/Optionen.

von Nop (Gast)


Lesenswert?

Dr. Sommer schrieb:

> Hinzu kommt auch noch die Byte-Reihenfolge. unions sind nunmal
> grundsätzlich nicht zur Konvertierung von Daten da

Type punning via unions ist doch mit C99 erlaubt?

von Dr. Sommer (Gast)


Lesenswert?

Nop schrieb:
> Type punning via unions ist doch mit C99 erlaubt?
Aber das Ergebnis ist nicht definiert (da plattformabhängig), es treten 
immer noch die Probleme mit Padding/Alignment und Byte-Reihenfolge auf.

Hier eine Möglichkeit wie man es (in C++) mittels Meta-Programmierung 
korrekt macht (mit immer garantiertem Ergebnis):

Beitrag "Re: Endianess beim Kopieren automatisieren"

von Kaj (Gast)


Lesenswert?

Unions and type-punning
https://stackoverflow.com/a/25672839
1
To re-iterate, type-punning through unions is perfectly fine in C (but
2
not in C++). In contrast, using pointer casts to do so violates C99
3
strict aliasing and is problematic because different types may have
4
different alignment requirements and you could raise a SIGBUS if you
5
do it wrong. With unions, this is never a problem.

von Holm T. (Gast)


Lesenswert?

Naja...das muß nicht wahnsinnig portabel sein, es geht nur um die 
Parametrisierung von Schrittmotorantrieben, konkret um ein paar 
Drehzahlen und was hier exemplarabhängig wird, die Anpassung an die 
Ungenauigkeiten der verwendeten Trapez-Spindeln.
Ich muß ein Progrämmchen machen mit dem die Mechaniker "im Feld" den 
Kram einstellen können. Es gibt nur den AVR der die Prominhalte ausliest 
und das Parametrisierungsprogramm das die Werte setzt. Ich muß das also 
nicht so portabel schreiben das es auch auf einem Z80 oder 6502 
funktioniert..der Aufwand sollte sich also auch in Grenzen halten.
Aus Erfahrung weiß ich aber nun auch das ein avr-gcc da auch schon ein 
moving Target ist.

Portabel war ein Lochstreifen..ich denke über Leser und Stanzer nach :-)

Gruß,

Holm

von Dr. Sommer (Gast)


Lesenswert?

Holm T. schrieb:
> Ich muß ein Progrämmchen machen mit dem die Mechaniker "im Feld" den
> Kram einstellen können.

Da liegt ja schon der Haken... was passiert wenn du das Programm mal für 
x86, mal für AMD64, mal mit GCC, mal mit MSVC kompilierst? Die musst du 
dazu bringen sich immer gleich zu verhalten... Die alten Mac's hatten ja 
Big Endian Prozessoren, bei denen wäre das Problem dann schon 
schwieriger geworden...

von Nop (Gast)


Lesenswert?

Dr. Sommer schrieb:

> Aber das Ergebnis ist nicht definiert (da plattformabhängig)

Nicht definiert oder implementationsabhängig? Ansonsten kann man auf 
64bit, wie schon gesagt, byteweise in das Array schieben. Man muß halt 
nur die Bytes (Endianess) an die richtige Stelle verfrachten.

Im simpelsten Fall setzt man auf AVR über das struct einmal jedes Byte 
und läßt sich das z.B. über die serielle ausgeben, wo welches Byte zu 
liegen kommt, ungefähr so (0x31 ist '1'):
1
void test_func(void)
2
{
3
  struct ee_struct
4
  {
5
  uint8_t bla;
6
  uint16_t fasel;
7
  uint32_t foo;
8
  int16_t bar;
9
  } ees;
10
  char test_array[10];
11
12
  ees.bla= 0x31;
13
  ees.fasel = 0x3233;
14
  ees.foo = 0x34353637;
15
  ees.bar = 0x3839;
16
  memcpy(test_array, &ees, 9);
17
  test_array[9] = 0;
18
19
  uart_put_string(test_array);
20
}

von Dr. Sommer (Gast)


Lesenswert?

Nop schrieb:
> Nicht definiert oder implementationsabhängig?

Ok, falsch ausgedrückt, es ist letzteres. Im C Standard ist nunmal kein 
Ergebnis definiert, bei der Nutzung von Bitshifts aber schon...

Nop schrieb:
> Ansonsten kann man auf 64bit, wie schon gesagt, byteweise in das Array
> schieben. Man muß halt nur die Bytes (Endianess) an die richtige Stelle
> verfrachten.

Oder man macht es gleich auf beiden Seiten so, dann ist's richtig.

von Nop (Gast)


Lesenswert?

Dr. Sommer schrieb:

> Oder man macht es gleich auf beiden Seiten so, dann ist's richtig.

Richtig schon, und sofern möglich, würde ich das auch bevorzugen. Dann 
ist der embedded code auch z.B. auf x86 algorithmisch testbar, und man 
fällt nicht auf die Nase, wenn in drei Jahren das Projekt mit einem 
anderen Controller laufen soll.

Wenn das aber aus Performancegründen nicht möglich ist, sondern die 
Daten direkt und gültig aus dem Flash gelesen werden müssen, dann wäre 
der kleine Hack von oben meine nächste Wahl.

von Dr. Sommer (Gast)


Lesenswert?

Nop schrieb:
> Wenn das aber aus Performancegründen nicht möglich ist
Wird von Compilern meistens wegoptimiert. Insbesondere auf 8 Bit 
Architekturen sollte sich nicht wirklich ein Unterschied ergeben...

von Dumpfbacke (Gast)


Lesenswert?

Hab ich was überlesen? Ich finde es nicht .....

Wenn die Endianess stimmt sollte man einen 32-Bit oder 64-Bit
Compiler mit
1
#pragma pack(1)

dazu bringen dem AVR entsprechend die richtige Union zu generieren.

von Dr. Sommer (Gast)


Lesenswert?

Dumpfbacke schrieb:
> Hab ich was überlesen? Ich finde es nicht .....

Dumpfbacke schrieb:
> #pragma pack(1)

Das ist eine Compiler spezifische Erweiterung. So was sollte man sowieso 
nur nutzen wenn's gar nicht anders geht. Gepackte structs gehen je nach 
Datentypen auf mancher Hardware gar nicht (zB mit uint64_t auf ARM). Bei 
x86 sollte es gehen (ist aber langsam).

von Dumpfbacke (Gast)


Lesenswert?

Dr. Sommer schrieb:
> Das ist eine Compiler spezifische Erweiterung.

Welcher weit verbreitete Compiler versteht das nicht?
Ohne zu wissen was der TO wirklich benutzt wette ich dass
sein Compiler das kann.

Dr. Sommer schrieb:
> So was sollte man sowieso
> nur nutzen wenn's gar nicht anders geht.

Dann lieber dämlich zu Fuss die Bytes fehlerträchtig
'rüberschieben, gell?

Dr. Sommer schrieb:
> Bei x86 sollte es gehen (ist aber langsam).

Der TO hat keine Ansprüche an die Geschwindigkeit gestellt.
Auf heutigen 64-Bit Systemen sollte diese kleine Daten-
verarbeitung auch keine wesentliche Rolle spielen.

von Dr. Sommer (Gast)


Lesenswert?

Dumpfbacke schrieb:
> Welcher weit verbreitete Compiler versteht das nicht?
Also ich programmiere normalerweise C(++) -Programme, keine 
GCC-Programme, und halte mich an die Gegebenheiten der Sprache, nicht 
die des Compilers...

Dumpfbacke schrieb:
> Dann lieber dämlich zu Fuss die Bytes fehlerträchtig
> 'rüberschieben, gell?
Eine Lösung wurde bereits genannt: 
Beitrag "Re: union mit Struktur und Array gleicher Größe?"
Damit funktioniert es garantiert immer korrekt auf allen Compilern und 
es ist auch nicht sonderlich fehlerträchtig.

Das übliche Phänomen hier, dass man partout nicht die korrekte Lösung 
nutzen will und sich lieber mit halben Spezial-Lösungen rumschlägt die 
manchmal funktionieren...

von Nop (Gast)


Lesenswert?

Dr. Sommer schrieb:

> Eine Lösung wurde bereits genannt:

Funktioniert nicht für C, da in C++.

> Das übliche Phänomen hier, dass man partout nicht die korrekte Lösung
> nutzen will

Byteweise Serialisierung ist korrekt und wird auch bei 
Host/Network-Problemen angewandt.

von Dr. Sommer (Gast)


Lesenswert?

Nop schrieb:
> Funktioniert nicht für C, da in C++.
Bin mir grad nicht sicher wo C verlangt wurde...?

Nop schrieb:
> Byteweise Serialisierung ist korrekt und wird auch bei
> Host/Network-Problemen angewandt.
Ja. Genau dazu rate ich ja auch, eben ggf. gekapselt damit es einfacher 
geht.

von Mikro 7. (mikro77)


Lesenswert?

Als klassische Serialisierung...
1
#include <stdio.h>
2
#include <inttypes.h>
3
4
struct eeprom
5
{
6
    uint8_t ui8 ;
7
    uint16_t ui16 ;
8
    uint32_t ui32 ;
9
    int16_t i16 ;
10
} ;
11
12
void serialize(long long value,size_t ndigits,unsigned char **p)
13
{
14
    for (size_t i=0 ; i<ndigits ; ++i)
15
    {
16
        (**p) = (unsigned char)value ;
17
        ++(*p) ;
18
        value >>= 8 ;
19
    }
20
}
21
22
long long deserialize(unsigned char const **p,size_t ndigits)
23
{
24
    long long value = 0 ;
25
    for (size_t i=0 ; i<ndigits ; ++i)
26
    {
27
        value <<= 8 ;
28
        --(*p) ;
29
        value |= (**p) ;
30
    }
31
    return value ;
32
}
33
34
int main()
35
{
36
    struct eeprom src = { 1,2,0xffffffff,-42 } ;
37
38
    unsigned char buffer[sizeof(struct eeprom)] ;
39
40
    unsigned char *p = buffer ;
41
    serialize(src.ui8, sizeof(src.ui8 ),&p) ; 
42
    serialize(src.ui16,sizeof(src.ui16),&p) ; 
43
    serialize(src.ui32,sizeof(src.ui32),&p) ; 
44
    serialize(src.i16 ,sizeof(src.i16 ),&p) ; 
45
46
    struct eeprom dst ;
47
    
48
    unsigned char const *q = p ;
49
    dst.i16  = (int16_t )deserialize(&q,sizeof(dst.i16 )) ; 
50
    dst.ui32 = (uint32_t)deserialize(&q,sizeof(dst.ui32)) ; 
51
    dst.ui16 = (uint16_t)deserialize(&q,sizeof(dst.ui16)) ; 
52
    dst.ui8  = (uint8_t )deserialize(&q,sizeof(dst.ui8 )) ; 
53
54
    printf("%u %u %x %d\n",src.ui8,src.ui16,src.ui32,src.i16) ;
55
    printf("%u %u %x %d\n",dst.ui8,dst.ui16,dst.ui32,dst.i16) ;
56
    
57
    return 0 ;
58
}
Eigentlich sollte man für jeden Datentyp eine separates Funktionspaar 
schreiben. Habe ich mir hier mal gespart. Habe auch nicht wirklich auf 
Bugs geprüft. Sollte aber zumindest die Idee zeigen.

von Kaj (Gast)


Lesenswert?

Dr. Sommer schrieb:
> Nop schrieb:
>> Funktioniert nicht für C, da in C++.
> Bin mir grad nicht sicher wo C verlangt wurde...?
Gar nicht. Ich verstehe es mehr als eine Anmerkung.

von Andreas M. (amesser)


Lesenswert?

Dr. Sommer schrieb:
> Nop schrieb:
>> Wenn das aber aus Performancegründen nicht möglich ist
> Wird von Compilern meistens wegoptimiert. Insbesondere auf 8 Bit
> Architekturen sollte sich nicht wirklich ein Unterschied ergeben...

Bei 8-Bittern mag das sein, auf einem ARM sieht das ganz anders aus. Ein 
byte-weises Laden und Zusammenbasteln eines 32 Bit Typs kostet 7 
Instruktionen (4 Loads, 3 ORs mit Shift).
Wenn man jedoch weis das die Daten sauber im Speicher ausgerichtet sind, 
dann kostet das genau eine Instruktion wenn man die Variable direkt 
lädt. Ich weis nicht ob der Compiler bei modernen ARMs (M3/M4) die auch 
Zugriffe auf ungerade Addressen können aus dem ganzen Byte-Gewurstel da 
automatisch einen Load/Store draus macht, bei uns war das jedoch gar 
nicht möglich, da die Daten aus den Peripherien kommen (Device Memory 
Attribute), dort sind solche unausgerichteten Zugriffe generell nicht 
möglich.

Wenn der Hauptteil der Software darin besteht solche Strukturen zu 
zerlegen und wieder zusammenzubasteln, z.B. in Kommunikationsanwendungen 
dann explodiert einem die Programmgröße. Ich habe vor ein paar Jahren 
dahingehend eine Optimierung machen müssen, das hat bei uns gut 100 kB 
Code gespart (bei etwa 500kB Codegröße) ...

von Holm T. (Gast)


Lesenswert?

Dr. Sommer schrieb:
> Holm T. schrieb:
>> Ich muß ein Progrämmchen machen mit dem die Mechaniker "im Feld" den
>> Kram einstellen können.
>
> Da liegt ja schon der Haken... was passiert wenn du das Programm mal für
> x86, mal für AMD64, mal mit GCC, mal mit MSVC kompilierst?

Das passiert definitiv nicht. Warum sollte ich ein solches Programm, das 
nur ein einziges Mal benötigt wird auf unterschiedlichen Architekturen 
mit unterschiedlichen Optimierungen bauen?

> Die musst du
> dazu bringen sich immer gleich zu verhalten... Die alten Mac's hatten ja
> Big Endian Prozessoren, bei denen wäre das Problem dann schon
> schwieriger geworden...

htons(), htonl(), ntohs(), ntohl()... :-)

Gruß,

Holm

von Dr. Sommer (Gast)


Lesenswert?

Andreas M. schrieb:
> Ein
> byte-weises Laden und Zusammenbasteln eines 32 Bit Typs kostet 7
> Instruktionen (4 Loads, 3 ORs mit Shift).
Meistens ist ein 4-Byte-Wert nicht auf 4 einzelne Bytes aufgeteilt, 
sondern 2x2Byte oder 1x1 + 1x3 Bytes, also 2 Loads und ein OR mit 
Shift...

Andreas M. schrieb:
> Wenn man jedoch weis das die Daten sauber im Speicher ausgerichtet sind
Der Punkt ist: Genau das sind sie nicht, weil sie über ein Interface 
reinkommen. Daher ja der ganze Aufwand.

Andreas M. schrieb:
> Ich weis nicht ob der Compiler bei modernen ARMs (M3/M4) die auch
> Zugriffe auf ungerade Addressen können aus dem ganzen Byte-Gewurstel da
> automatisch einen Load/Store draus macht
m.W. nicht.

Andreas M. schrieb:
> Wenn der Hauptteil der Software darin besteht solche Strukturen zu
> zerlegen und wieder zusammenzubasteln
Was soll man machen? Man muss nunmal irgendein Protokoll definieren, und 
das muss man wohl oder übel (ent)packen. Die Frage ist lediglich ob man 
es korrekt (Bitshifts) oder gefrickelt (unions/casts) macht.

Holm T. schrieb:
> Warum sollte ich ein solches Programm, das
> nur ein einziges Mal benötigt wird auf unterschiedlichen Architekturen
> mit unterschiedlichen Optimierungen bauen?
Es kommt vor, dass Projekte wachsen und neue Wünsche kommen. Wenn es 
dann heißt "portier das doch mal auf ein Android-Tablet" beißen einen 
solche Schnellschuss-Lösungen schnell in den Hintern...

Holm T. schrieb:
> htons(), htonl(), ntohs(), ntohl()... :-)
Das sind POSIX-Funktionen (schon wieder irgendeine nichtportable 
Erweiterung). Wie gesagt, Bitshifts sind portabel, und auch eleganter, 
denn: Bei Bitshifts schreibt man hin was man machen will und der 
Compiler sorgt dafür dass es richtig gemacht wird. Bei htons & Co 
schreibt man eine eventuell nötige Operation hin die ggf. ausgeführt 
wird - m.E. komplizierter und fehlerträchtiger.

Mikro 77 hat gezeigt wie es in C richtig, portabel, ohne Erweiterungen 
geht. Der genannte C++ Code ist mehr oder weniger ein Wrapper um genau 
dieses Vorgehen.

PS: Ich sollte mal hier einen Wiki-Artikel verfassen, der das ein für 
alle Mal (die Frage kommt ja alle paar Wochen) korrekt zusammenfasst und 
die richtige Lösung zeigt...

von Andreas M. (amesser)


Lesenswert?

Dr. Sommer schrieb:
> Andreas M. schrieb:
>> Ein
>> byte-weises Laden und Zusammenbasteln eines 32 Bit Typs kostet 7
>> Instruktionen (4 Loads, 3 ORs mit Shift).
> Meistens ist ein 4-Byte-Wert nicht auf 4 einzelne Bytes aufgeteilt,
> sondern 2x2Byte oder 1x1 + 1x3 Bytes, also 2 Loads und ein OR mit
> Shift...

Das verstehe ich nicht. Bei mir besteht ein 32-Bit Wert in jedem 
Raw-Blob aus 4 einzelnen Bytes. Sobald ich von zwei Byte oder drei Byte 
spreche lege ich schon implizit eine Struktur des Raw-Blobs fest.
Wenn dieser Raw Blob beim Deserialisieren vom Programmcode in Form von 
einzelnen Bytes eingelesen wird, dann benötige ich genau vier 
Bytezugriffe plus Schiebeoperations und OR's:

Mit Byte-Zugriffen meine ich:
1
uint8_t raw[4];
2
uint32_t value;
3
4
value = ((uint32_t)raw[0]  << 24) |
5
        ((uint32_t)raw[1]  << 16) |
6
        ((uint32_t)raw[2]  <<  8) |
7
        ((uint32_t)raw[3]  <<  0);

sieht dann in thumb (ARM966) übersetzt in etwa so aus (Kopiert aus
original code, nicht obiges beispiel, aber identischer Aufbau,
Mit Thumb-2 kann man die Shifts in die Orrs reinkombinieren):
1
  be:  7be0        ldrb  r0, [r4, #15]
2
  c0:  7ba3        ldrb  r3, [r4, #14]
3
  c2:  0200        lsls  r0, r0, #8
4
  c4:  4318        orrs  r0, r3
5
  c6:  7c23        ldrb  r3, [r4, #16]
6
  c8:  041b        lsls  r3, r3, #16
7
  ca:  4318        orrs  r0, r3
8
  cc:  7c63        ldrb  r3, [r4, #17]
9
  ce:  061b        lsls  r3, r3, #24
10
  d0:  4318        orrs  r0, r3

Vieleicht kannst Du erklären was du mit im 2x2Byte oder 1+1x3Byte 
meinst?

von Dr. Sommer (Gast)


Lesenswert?

Andreas M. schrieb:
> Wenn dieser Raw Blob beim Deserialisieren vom Programmcode in Form von
> einzelnen Bytes eingelesen wird, dann benötige ich genau vier
> Bytezugriffe plus Schiebeoperations und OR's:
Na, wenn die 4 Bytes aufeinander folgen, sind 3 davon in einem Word, und 
das restliche im vorherigen oder darauffolgenden Word. Bzw 2 und 2. Da 
muss man nicht jedes Byte einzeln lesen, sondern kann mit LDRH 2 auf 
einmal lesen. Ich hab mich aber vertan, in der 3+1 Variante sind es 3 
Lesezugriffe (LDRH+LDRB+LDRB).

Andreas M. schrieb:
> Ich weis nicht ob der Compiler bei modernen ARMs (M3/M4) die auch
> Zugriffe auf ungerade Addressen können aus dem ganzen Byte-Gewurstel da
> automatisch einen Load/Store draus macht
Das habe ich jetzt doch mal ausprobiert, und siehe da: Der GCC kann's!
Das:
1
uint32_t test (uint8_t* p) {
2
  uint32_t a = p[1];
3
  uint32_t b = p[2];
4
  uint32_t c = p[3];
5
  uint32_t d = p[4];
6
  return a | (b << 8) | (c << 16) | (d << 24);
7
}
Wird zu:
1
00000000 <test(unsigned char*)>:
2
   0:  f8d0 0001   ldr.w  r0, [r0, #1]
3
   4:  4770        bx  lr
Das ist natürlich effizienter als mehrere einzelne Lesezugriffe, aber 
nicht so effizient wie ein korrekt ausgerichteter Zugriff, und falls man 
unaligend Zugriffe im Prozessor abgeschaltet hat, gibts einen Fault - in 
dem Fall kann man dieses Verhalten mit -mno-unaligned-access abschalten.

Das zeigt also - mit diesem Code beschreibt man korrekt und portabel 
einen Little-Endian-uint32_t -Zugriff in einem "raw" Array, welcher je 
nach Plattform sogar optimiert wird. Kann er nicht optimiert werden, 
funktioniert er dennoch richtig (dann hätte man ohnehin keine Wahl es 
anders zu machen).

von Andreas M. (amesser)


Lesenswert?

Dr. Sommer schrieb:
> Andreas M. schrieb:
>> Wenn dieser Raw Blob beim Deserialisieren vom Programmcode in Form von
>> einzelnen Bytes eingelesen wird, dann benötige ich genau vier
>> Bytezugriffe plus Schiebeoperations und OR's:
> Na, wenn die 4 Bytes aufeinander folgen, sind 3 davon in einem Word, und
> das restliche im vorherigen oder darauffolgenden Word. Bzw 2 und 2. Da
> muss man nicht jedes Byte einzeln lesen, sondern kann mit LDRH 2 auf
> einmal lesen.

Und woher genau soll der Compiler wissen, wenn er in einer Funktion ein 
byte-pointer übergeben bekommt, ob dieser auf eine 0er, 1er 2er oder 3er 
addresse zeigt? Das müsste zur Laufzeit geprüft werden und dann 
entsprechend spezifischer code angesprungen werden. Das ist teurer als 
einfach gleich byte-zugriffe zu machen.

>
> Andreas M. schrieb:
>> Ich weis nicht ob der Compiler bei modernen ARMs (M3/M4) die auch
>> Zugriffe auf ungerade Addressen können aus dem ganzen Byte-Gewurstel da
>> automatisch einen Load/Store draus macht
> Das habe ich jetzt doch mal ausprobiert, und siehe da: Der GCC kann's!
> Das:
>
1
uint32_t test (uint8_t* p) {
2
>   uint32_t a = p[1];
3
>   uint32_t b = p[2];
4
>   uint32_t c = p[3];
5
>   uint32_t d = p[4];
6
>   return a | (b << 8) | (c << 16) | (d << 24);
7
> }
> Wird zu:
>
1
00000000 <test(unsigned char*)>:
2
>    0:  f8d0 0001   ldr.w  r0, [r0, #1]
3
>    4:  4770        bx  lr
> Das ist natürlich effizienter als mehrere einzelne Lesezugriffe, aber
> nicht so effizient wie ein korrekt ausgerichteter Zugriff, und falls man
> unaligend Zugriffe im Prozessor abgeschaltet hat, gibts einen Fault - in
> dem Fall kann man dieses Verhalten mit -mno-unaligned-access abschalten.

Ja mit modernen Cortex M3/M4 funktioniert geht das so. Allerdings nicht, 
wenn p auf "Device Memory" zeigt. Dort gehen solche Zugriffe 
grundsätzlich nicht, egal ob in der CPU aktiviert oder nicht. In meinem 
Anwendungsfall liegen die Ethernet Frames in genau solchem 
Device-Memory, ich bin also auf Byte-Zugriff angewiesen, es sei denn ich 
weis wo mein Basis-Zeiger liegt.

von Dr. Sommer (Gast)


Lesenswert?

Andreas M. schrieb:
> Und woher genau soll der Compiler wissen, wenn er in einer Funktion ein
> byte-pointer übergeben bekommt, ob dieser auf eine 0er, 1er 2er oder 3er
> addresse zeigt?
Ja bei einem Parameter gehts wohl nicht. Bei globalen Arrays mit 
explizitem Alignment wäre es denkbar, aber das kann der GCC wohl auch 
nicht.

Andreas M. schrieb:
> Dort gehen solche Zugriffe
> grundsätzlich nicht, egal ob in der CPU aktiviert oder nicht.
Ja das stimmt.

Andreas M. schrieb:
> ich bin also auf Byte-Zugriff angewiesen
Genau. Der Zugriff per union/cast wäre aber gar nicht erst möglich, 
daher ist der Zugriff mit einzelnen Bytes nicht ineffizienter. Also auch 
hier kein Argument für union/cast. Oder was war nochmal deine Aussage?

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Holm T. schrieb:
> struct eeprom_val
>     {
>       uint8_t bla;
>       uint16_t fasel;
>       uint32_t foo;
>       int16_t bar;
>     }ee-val;

Vermutlich ee_val?

> Wie zur Hölle vereinbare ich nun eine union die die Struktur und ein
> Array gleicher Länge enthält, die mir Zugriff auf den Speicherbereich
> er Struktur ermöglicht?

Das brauchst du garnicht.

Alles was du für eeprom_read_block und eeprom_write_block brauchst ist 
die Startadresse (&ee_val) sowie die Größe (sizeof(ee_val) oder 
sizeif(struct eeprom_val)).


Wenn du die Init-Werte auf dem Host erzeugst, dann erzeug doch einfach 
einen passenden Initializer.  Das Host-Formal ist doch egal.

avr:
1
ee_val = { 0x12, 12345, ... };

Host:
1
fprintf (init_file, "ee_val = { 0x%02x, %u, ... };\n", wert_bla, ...);

von Holm T. (Gast)


Lesenswert?

Ich habe dann aber doppelte, einzeln zu wartende  Definitionen für die 
selbe Datenstruktur, genau das wollte ich vermeiden.

Gruß,
Holm

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Holm T. schrieb:
> Ich habe dann aber doppelte, einzeln zu wartende  Definitionen für die
> selbe Datenstruktur, genau das wollte ich vermeiden.

Du willst eine Struktur initialisieren und den Initializer auf einem PC 
generieren.  Oder du sprichst in Räteln...

von Holm T. (Gast)


Lesenswert?

Da ist nicht viel Rätselhaftes dran. Da ich die Firmware für den Atmel 
ja auch geschrieben habe, möchte ich die selben Header verwenden.

Gruß,

Holm

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Holm T. schrieb:
> Da ist nicht viel Rätselhaftes dran. Da ich die Firmware für den Atmel
> ja auch geschrieben habe, möchte ich die selben Header verwenden.

Kannst da ja auch, solchge der daraus generierte Coden nicht auf einem 
AVR laufen soll.  Wenn du Daten generierst, kannst du mit allem 
moglichen Gezauber AVR-Kompatibilität herstellen, aber du kannst duch 
genauso die gewünschten Initializer erzeugen.  Eine Vorstellung, wie die 
C-Objekte aussehen und welche Felder sie haben und wie sie initialisiert 
werden sollen brauchst du doch ohnehin?

von Holm T. (Gast)


Lesenswert?

Johann L. schrieb:
> Holm T. schrieb:
>> Da ist nicht viel Rätselhaftes dran. Da ich die Firmware für den Atmel
>> ja auch geschrieben habe, möchte ich die selben Header verwenden.
>
> Kannst da ja auch, solchge der daraus generierte Coden nicht auf einem
> AVR laufen soll.

Das ist selbstverständlich.

> Wenn du Daten generierst, kannst du mit allem
> moglichen Gezauber AVR-Kompatibilität herstellen, aber du kannst duch
> genauso die gewünschten Initializer erzeugen.  Eine Vorstellung, wie die
> C-Objekte aussehen und welche Felder sie haben und wie sie initialisiert
> werden sollen brauchst du doch ohnehin?

Ja.

Ich möchte nur die Datenstruktur an sich nur in einer Datei warten die 
von beiden Compilern eingebunden wird. Die Unterschiede der beiden 
Architekturen sind mir schon klar.

Gruß,

Holm

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.