Forum: Mikrocontroller und Digitale Elektronik Blöder Compiler - Optimieren in C?


von Timm T. (Gast)


Lesenswert?

Ist der Compiler hier wirklich so blöd? Ich will eigentlich nur aus 2 
Bytes aus dem TWI-Empfang einen 16bit-Wert erzeugen.

Das macht der Compiler draus, Einstellung -Os:
1
  bmp_ac1 = twi_readack() << 8 | twi_readack();  // Eeprom Daten auslesen 11 x 2 Byte
2
 200:  72 df         rcall  .-284      ; 0xe6 <twi_readack>
3
 202:  c8 2f         mov  r28, r24
4
 204:  70 df         rcall  .-288      ; 0xe6 <twi_readack>
5
 206:  2c 2f         mov  r18, r28
6
 208:  30 e0         ldi  r19, 0x00  ; 0
7
 20a:  32 2f         mov  r19, r18
8
 20c:  22 27         eor  r18, r18
9
 20e:  28 2b         or  r18, r24
10
 210:  30 93 89 00   sts  0x0089, r19
11
 214:  20 93 88 00   sts  0x0088, r18
12
  bmp_ac2 = twi_readack() * 256 + twi_readack();
13
 218:  66 df         rcall  .-308      ; 0xe6 <twi_readack>
14
 21a:  c8 2f         mov  r28, r24
15
 21c:  64 df         rcall  .-312      ; 0xe6 <twi_readack>
16
 21e:  2c 2f         mov  r18, r28
17
 220:  30 e0         ldi  r19, 0x00  ; 0
18
 222:  32 2f         mov  r19, r18
19
 224:  22 27         eor  r18, r18
20
 226:  28 0f         add  r18, r24
21
 228:  31 1d         adc  r19, r1
22
 22a:  30 93 73 00   sts  0x0073, r19
23
 22e:  20 93 72 00   sts  0x0072, r18

So hätte ich das gemacht:
1
  bmp_ac1 = twi_readack() << 8 | twi_readack();  // Eeprom Daten auslesen 11 x 2 Byte
2
 200:  72 df         rcall  .-284      ; 0xe6 <twi_readack>
3
 202:        sts  0x0089, r28
4
 204:  70 df         rcall  .-288      ; 0xe6 <twi_readack>
5
 206:           sts  0x0088, r28

Kann man dem Compiler beibringen, hier noch etwas intelligenter zu 
optimieren?

(avr-gcc im Atmel Studio 7, Atmega8, Compileroption auf -Os, -O2 bringt 
gleiches Ergebnis)

von chris (Gast)


Lesenswert?

Timm T. schrieb:
> Kann man dem Compiler beibringen, hier noch etwas intelligenter zu
> optimieren?

Gegenfrage:
Wie lange dauern deine beiden I2C-Transfers?
Und wie lange dauern im Vergleich dazu ein paar zusätzliche 
Prozessortakte wegen nicht ganz optimalem Code?

von Peter II (Gast)


Lesenswert?

was für Datentypen verwendest du?

von Michael B. (laberkopp)


Lesenswert?

Timm T. schrieb:
> Ist der Compiler hier wirklich so blöd?

Ja.

union
{
    struct { uint8_t b0, b1; } bytewise
    uint16_t wordwise;
}
bmp_ac1;

bmp_ac1.bytewise.b0=twi_readack();
bmp_ac1.bytewise.b1=twi_readack();
...bmp_ac1.wordwise;

von Rene H. (Gast)


Lesenswert?

Und wo ist Dein OR?

von avr (Gast)


Lesenswert?

chris schrieb:
> Gegenfrage:
> Wie lange dauern deine beiden I2C-Transfers?
> Und wie lange dauern im Vergleich dazu ein paar zusätzliche
> Prozessortakte wegen nicht ganz optimalem Code?

Darum geht es doch gar nicht. Sondern eher darum, dass jedes mal 6 
Befehle (hier 12 Bytes) verschwendet werden.
Seit wann ist das Aufzeigen bzw. Hinterfragen von fragwürdiger bis zu 
nicht vorhandener Optimierung verboten? Nur so können Compiler auch 
besser werden.

von (prx) A. K. (prx)


Lesenswert?

Timm T. schrieb:
> So hätte ich das gemacht:

Das Ergebnis des Ausdrucks ist eine 16-Bit Variable. Dementsprechend 
wird der Code erzeugt. Dein Code behandelt sie als 2 getrennte 8-Bit 
Variablen. Allerdings ist trotzdem recht viel Raum für Verbesserungen. 
Optimierung von Operationen auf Teilworte ist nicht die stärkste Seite 
von GCC. Ist zwar wesentlich besser geworden (da hat wohl Johann einigen 
Anteil), aber perfekt ist das nicht.

Der Compiler weiss möglicherweise nicht, ob die Funktion diese Variable 
nutzt. Wäre dies der Fall gäbe es in deinem Code Datensalat.

Unabhängig davon kann es dennoch Datensalat geben, denn in deinem 
Ausdruck ist offen, welcher Aufruf zuerst erfolgt. Der linke oder der 
rechte.

von Timm T. (Gast)


Lesenswert?

A. K. schrieb:
> Der Compiler weiss möglicherweise
> nicht, ob die Funktion diese Variable nutzt.

Das könnte er ja eruieren. Die Funktion ist kein Geheimnis.

A. K. schrieb:
> Unabhängig davon kann es dennoch Datensalat geben, denn in deinem
> Ausdruck ist undefiniert, welcher Aufruf zuerst erfolgt.

Ja, das macht mir auch Sorge, vor allem wenn es jetzt funktioniert, 
heißt das ja nicht, daß das eine andere Compilerversion oder eine andere 
Compilereinstellung nicht anders macht.

chris schrieb:
> Und wie lange dauern im Vergleich dazu ein paar zusätzliche
> Prozessortakte wegen nicht ganz optimalem Code?

Es geht nicht um die Takte, die Zuweisung erfolgt 11 mal, da kommen 
einige Byte an sinnlosen Befehlen zusammen.

Michael B. schrieb:
> union
> {
>     struct { uint8_t b0, b1; } bytewise
>     uint16_t wordwise;
> }
> bmp_ac1;
>
> bmp_ac1.bytewise.b0=twi_readack();
> bmp_ac1.bytewise.b1=twi_readack();
> ...bmp_ac1.wordwise;

Danke, die Logik dahinter ist mir verständlich, aber das schreit 
irgendwie nach: Dann kann ichs auch gleich wieder in ASM machen.

von Rene H. (Gast)


Lesenswert?

Rene H. schrieb:
> Und wo ist Dein OR?

Der Compiler kann nur begrenzt "intelligent" sein. Deine Variante ist 
maschinell machbar, aber ein extremfall.

von (prx) A. K. (prx)


Lesenswert?

Timm T. schrieb:
> Dann kann ichs auch gleich wieder in ASM machen.

Wenn du stets erwartest, dass der Compiler es genauso tut wie du in 
Assembler, dann solltest du keinen Compiler verwenden, sondern 
Assembler. Das schont deine Nerven.

Von grossen Maschinen stammende Compiler wie GCC gehen in weiten Teilen 
davon aus, dass die Maschine Wortoperationen beherrscht, also 
Operationen auf int/unsigned Grundoperationen der Maschine sind. Im 
Prinzip ist C so definiert.

Bei fast allen Zielmaschinen ist der auch der Fall, bei AVR nicht. 
Optimierungen, die wie hier auf effektiv kombinierte Teilwortoperationen 
abzielen, werden dir deshalb immer irgendwo fehlen.

von Timm T. (Gast)


Lesenswert?

Datentypen: Die twi_read geben uint_8 zurück, die bmp_xx sind uint_16 
oder int_16, macht aber keinen Unterschied im Compilat.

Ich werds wahrscheinlich in eine twi_readword-Funktion packen, dann ist 
die Reihenfolge auch eindeutig.

Michael B. schrieb:
> union
> {
>     struct { uint8_t b0, b1; } bytewise
>     uint16_t wordwise;
> }
> bmp_ac1;

Meine Idee war, das in ein Bytearray zu mappen, dann könnte ich das in 
einem Rutsch mit einer Schleife auslesen.

Ich brauch aber in den nachfolgenden Berechnungen (Bosch Drucksensor 
BMP180) die einzelnen Variablen.

Das müßte dann so aussehen:
1
array = [ac1high, ac1low, ac2high, ac2low ... mdhigh, mdlow]
2
Zugriffe (int_16 ac1)     (int_16 ac2)    ... (int_16 md)

Geht sowas?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Timm T. schrieb:
> Ist der Compiler hier wirklich so blöd?

Welcher denn eigentlich?

Und nein, bitte nicht „GCC“ als Antwort, sondern die genaue Version
des Compilers sowie die benutzten Optionen.

von (prx) A. K. (prx)


Lesenswert?

NB: Lokale skalare Variablen sind meist effizienter als globale.

von Peter D. (peda)


Lesenswert?

Timm T. schrieb:
> Es geht nicht um die Takte, die Zuweisung erfolgt 11 mal, da kommen
> einige Byte an sinnlosen Befehlen zusammen.

Dann bist Du selber der noch größere Codeverschwender, weil Du keine 
Schleife draus machst.
Gegen Copy&Paste Verschwendung kann auch der beste Compiler nichts 
ausrichten.

Schon wenn Sachen zweimal auftreten, überlege ich, ob ich ne Schleife 
oder wenigstens ne Funktion draus mache. Das reduziert auch die 
Fehlermöglichkeiten, da dann ein Fehler nur an einer Stelle gemacht 
werden kann.

von Staubfänger (Gast)


Lesenswert?

Wenn ich schon den Titel sehe, kommt mir der Kaffee hoch.

Einfach mal ein paar Monate probieren einen eigenen Compiler zu basteln 
und dann ermässigt sich die empörte Frage auf ein: "Wünsche mir Patch 
für Optimierung im Falle der Kombination zweier Funktionsresulate" oder 
so ähnlich.

Dann noch die Frage im zu dem Compiler passenden Forum posten oder auch 
als "Improvement request".

Denn die Frage ob das geht, lässt sich allenfalls bejahen oder verneinen 
und das reicht dem TO sicher nicht.

So ist das reine Frust-Abladerei. Und sind wir hier die Mülltonnen für 
anderer Leute Frustration? Nein!

Meckern ist leicht. Besser machen dann schon nicht mehr.

von Timm T. (Gast)


Lesenswert?

A. K. schrieb:
> Lokale Variablen sind meist effizienter als globale.

Ich muß aber die Kalibrierdaten am Anfang einlesen und dann immer wieder 
damit rechnen. Da hätte ich jetzt keine Idee, wie ich die lokal 
verarbeiten soll, außer sie jedesmal neu einzulesen.

Jörg W. schrieb:
> Welcher denn eigentlich?

Invoking: AVR/GNU C Compiler : 4.9.2
"C:\Program Files 
(x86)\Atmel\Studio\7.0\toolchain\avr8\avr8-gnu-toolchain\bin\avr-gcc.exe 
"   -x c -funsigned-char -funsigned-bitfields -DDEBUG  -I"C:\Program 
Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.0.90\include"  -Os 
-ffunction-sections -fdata-sections -fpack-struct -fshort-enums -g2 
-Wall -mmcu=atmega8 -B "C:\Program Files 
(x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.0.90\gcc\dev\atmega8" -c 
-std=gnu99 -MD -MP -MF "gh_main.d" -MT"gh_main.d" -MT"gh_main.o"   -o 
"gh_main.o" ".././gh_main.c"

Invoking: AVR/GNU Linker : 4.9.2
"C:\Program Files 
(x86)\Atmel\Studio\7.0\toolchain\avr8\avr8-gnu-toolchain\bin\avr-gcc.exe 
"  -o gh.elf  gh_main.o   -Wl,-Map="gh.map" -Wl,--start-group -Wl,-lm 
-Wl,--end-group -Wl,--gc-sections -mmcu=atmega8 -B "C:\Program Files 
(x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.0.90\gcc\dev\atmega8"

von (prx) A. K. (prx)


Lesenswert?

Staubfänger schrieb:
> Wenn ich schon den Titel sehe, kommt mir der Kaffee hoch.

Um diese Uhrzeit? ;-)

> So ist das reine Frust-Abladerei.

Ja. Aber dafür wiederum solltest du die Nerven haben, es ggf. zu 
ignorieren.

von avr (Gast)


Lesenswert?

Das Problem scheint zu sein, dass der GCC ungern/nie Variablen im Ram 
manipuliert ohne sie komplett in die Register zu lesen.

So führen solche Konstrukte, die eigentlich genau das bezwecken sollen, 
zu schlimmen Code:
1
out = out & 0xff00 | twi_readack();
1
  80:  c0 91 00 01   lds  r28, 0x0100
2
  84:  d0 91 01 01   lds  r29, 0x0101
3
  88:  cc 27         eor  r28, r28     
4
  8a:  e5 df         rcall  .-54       ; 0x56 <twi_readack>
5
  8c:  c8 2b         or  r28, r24
6
  8e:  d0 93 01 01   sts  0x0101, r29
7
  92:  c0 93 00 01   sts  0x0100, r28
Auch ein einfaches
1
out &= 0xff;
 führt zu
1
  
2
  bc:  89 81         ldd  r24, Y+1  ; 0x01
3
  be:  9a 81         ldd  r25, Y+2  ; 0x02
4
  c0:  99 27         eor  r25, r25
5
  c2:  9a 83         std  Y+2, r25  ; 0x02
6
  c4:  89 83         std  Y+1, r24  ; 0x01
Am besten scheint die Variante mit temporären Variable zu sein. Damit 
umgeht man das Problem, aber der Code wird auch ein Stück ineffizienter 
als möglich:
1
  uint16_t temp;
2
  temp = twi_readack();
3
  5e:  fb df         rcall  .-10       ; 0x56 <twi_readack>
4
  60:  c8 2f         mov  r28, r24
5
  62:  d0 e0         ldi  r29, 0x00  ; 0
6
  temp |= twi_readack() << 8;
7
  64:  f8 df         rcall  .-16       ; 0x56 <twi_readack>
8
  66:  d8 2b         or  r29, r24
9
  out = temp;
10
  68:  d0 93 01 01   sts  0x0101, r29
11
  6c:  c0 93 00 01   sts  0x0100, r28

Asm-Code kommt hier von AVR/GNU C Compiler : 4.8.1


Staubfänger schrieb:
> Meckern ist leicht. Besser machen dann schon nicht mehr.

Und du meckerst hier am meisten. Der Beitrag von TO ist um 
Größenordnungen sinnvoller als dein Erguss.

von (prx) A. K. (prx)


Lesenswert?

Timm T. schrieb:
> Da hätte ich jetzt keine Idee, wie ich die lokal
> verarbeiten soll, außer sie jedesmal neu einzulesen.

Ich kenne deinen Code nicht. Das war nur ein allgemeiner Hinweis.

von Timm T. (Gast)


Lesenswert?

Staubfänger schrieb:
> Wenn ich schon den Titel sehe, kommt mir der Kaffee hoch.

Ich zitiere Dich dann, wenn es im Forum wieder mal heißt: C ist ja sooo 
viel besser als ASM.

Weißt Du, da kommt MIR der Kaffee hoch. Ich wüßte sofort, wie ich es in 
ASM machen müßte und hätte es schon fertig. Aber ich bin ja lernwillig, 
und ich bin auch bereit mal über den Tellerrand zu schauen, und mal was 
Neues auszuprobieren. Aber wenn ich dann ne Frage hab: Ist das normal 
so, oder geht das noch besser? werd ich blöd angemacht.

Peter D. schrieb:
> Dann bist Du selber der noch größere Codeverschwender, weil Du keine
> Schleife draus machst.

Ist ja nicht so, daß ich die Idee nicht auch schon hatte. Und wie 
bekomme ich die Werte aus der Schleife dann in 11 verschiedene 
Variablen?

Wie gesagt, ich würde es in ein Bytearray schreiben, und die einzelnen 
Bytes jeweils wordweise auf die Variablen mappen. Also in ASM würde ich 
das so machen. In C weiß ich nicht, ob und wie das geht.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Timm T. schrieb:
> Invoking: AVR/GNU C Compiler : 4.9.2

OK, zumindest erstmal nicht hornalt. ;-)

Ansonsten das übliche Blahblah an Atmel-Studio-generierten Optionen,
teilweise zweifelhaft (-funsigned-char), teilweise unsinnig
(-fpack-struct, hält sich ungemein hartnäckig, obwohl es bei einer
8-Bit-Architektur absolut unnütz ist, denn die werden immer gepackt).

avr schrieb:
> Am besten scheint die Variante mit temporären Variable zu sein.

Ja, temporäre uint8_t-Variable verhelfen dem Compiler häufig zur
klareren Erkenntnis, dass die vorgeschriebenen Regeln für die integer
promotion hier gar nicht nötig sind.  Bei komplexeren Ausdrücken
scheint der Compiler schneller den Überblick zu verlieren, in welchen
Fällen die 16 Bits tatsächlich nötig sind.

von (prx) A. K. (prx)


Lesenswert?

avr schrieb:
> Das Problem scheint zu sein, dass der GCC ungern/nie Variablen im Ram
> manipuliert ohne sie komplett in die Register zu lesen.

Im Zwischencode und in weiten Teilen der Optimierung sind das 
Wortoperationen, keine Byteoperationen. Zu Bytebefehlen werden sie erst 
recht spät in der Erzeugung von AVR Code, oft recht schematisch, weshalb 
man - wie hier - die 16-Bit Operationen erkennen kann:

 200:  72 df         rcall  .-284      ; 0xe6 <twi_readack>
 202:  c8 2f         mov  r28, r24
temp1_8bit = ...

 204:  70 df         rcall  .-288      ; 0xe6 <twi_readack>
temp2_8bit = ...

 206:  2c 2f         mov  r18, r28
 208:  30 e0         ldi  r19, 0x00  ; 0
temp3_16bit = extend temp1_8bit

 20a:  32 2f         mov  r19, r18
 20c:  22 27         eor  r18, r18
temp4_16bit = temp3_16bit * 256

 20e:  28 2b         or  r18, r24
temp5_16bit = temp4_16bit | ...
Hier ist die einzige Optimierung sichtbar.

von Staubfänger (Gast)


Lesenswert?

avr schrieb:
[...]

> Staubfänger schrieb:
>> Meckern ist leicht. Besser machen dann schon nicht mehr.
>
> Und du meckerst hier am meisten. Der Beitrag von TO ist um
> Größenordnungen sinnvoller als dein Erguss.

Das kann man so oder so sehen.
Eine sachliche Darstellung des Problems leidet doch sehr, wenn sie mit 
einer unsachlichen Qualifizierung eingeleitet wird. Ausserdem fällt mir 
der TO nicht zum ersten Mal negativ auf.

Aber danke für Deine Mitteilung.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Timm T. schrieb:
> In C weiß ich nicht, ob und wie das geht.

Wurde dir ja schon gezeigt: mit einer union.

Ist halt nicht portabel, aber das wäre deine Assemblerlösung auch
nicht, folglich muss eine C-Lösung es ebenfalls nicht sein.

Ansonsten: gib' uns compilierfähigen Code, dann können wir dir bessere
Ratschläge erteilen. ;)

von (prx) A. K. (prx)


Lesenswert?

Timm T. schrieb:
> Ich zitiere Dich dann, wenn es im Forum wieder mal heißt: C ist ja sooo
> viel besser als ASM.

Dann tu dich mit unserem bekannten ASM-Fan zusammen und lass C sein. 
Oder akzeptiere, dass ein kostenloser Compiler oft nicht jenen Code 
erzeugen wird, den du gerne sehen willst. Oder nur nach aufwändigem 
tweaking.

Vielen Leuten kommt es nicht darauf an, das letzte Byte Code 
einzusparen, sondern Entwicklungszeit einzusparen. Wenn dabei etwas mehr 
Code rauskommt als in Assembler, dann ist das eben so.

von Timm T. (Gast)


Lesenswert?

Jörg W. schrieb:
> Wurde dir ja schon gezeigt: mit einer union.

Mit einer union von Bytes und Words. Aber geht das auch auf ein Array?

Das interessiert mich prinzipiell, weil ich das schon mehrfach gebraucht 
hätte, aber in den Tuts sind nur die einfachen unions zwischen Variablen 
erklärt.

Oder könnte man die Variablen zu einer Structure zusammenfassen und die 
Structure auf ein Array pappen?

Die Idee ist prinzipiell: Lese alle Daten in einem Rutsch mit einer 
Schleife in das Array, greife als Variable auf das Array zu.

von aSma>> (Gast)


Lesenswert?

"meine mama sagt dumm ist der der dummes tut" (forrest gump)

von (prx) A. K. (prx)


Lesenswert?

Timm T. schrieb:
> Oder könnte man die Variablen zu einer Structure zusammenfassen und die
> Structure auf ein Array pappen?

Grundprinzip:

union {
  uint8_t bytes[12];
  uint16_t words[6];
};

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Timm T. schrieb:
> Mit einer union von Bytes und Words. Aber geht das auch auf ein Array?

Klar kannst du auch ein Array von Unions anlegen:
1
struct b8_i16 {
2
   uint8_t b[2];
3
   uint16_t i;
4
};
5
6
struct b8_i16 array[42];

OK, aber dann müsstest du die beiden Bytes eines 16-Bit-Halbwortes
natürlich immer noch einzeln adressieren (b[0] und b[1]).

Was natürlich auch geht, nicht mehr, aber auch nicht weniger portabel:
1
struct array {
2
   uint8_t b[42];
3
   uint16_t i[21];
4
};
5
6
struct array a;

Nachteil: die Gesamtgröße ist schon im Datentyp codiert, du kannst
den also nur genau für ein Array dieser Größe benutzen.  Wenn du
40 Bytes (20 Halbworte) brauchst, musst du einen neuen Typen anlegen.

von Timm T. (Gast)


Lesenswert?

A. K. schrieb:
> Dann tu dich mit unserem bekannten ASM-Fan zusammen und lass C sein.

Ja sorry, daß das C-Implantat bei mir leider vergessen wurde. Ich weiß, 
manche Leute können C schon von Geburt an und müssen nichts mehr lernen.

A. K. schrieb:
> Vielen Leuten kommt es nicht darauf an, das letzte Byte Code
> einzusparen, sondern Entwicklungszeit einzusparen. Wenn dabei etwas mehr
> Code rauskommt als in Assembler, dann ist das eben so.

Mir kommt es auch nicht auf das letzte Byte an. Mir geht es darum, etwas 
zu lernen. Vielleicht gibt es einfach eine bessere Möglichkeit, die 
Aufgabe zu formulieren, bei der der dumme Compiler effizienter arbeitet.

Du mußt hier nicht die Ehre der Maschinen verteidigen, das machen die in 
Kürze sowieso selber.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Timm T. schrieb:
> So hätte ich das gemacht:
>   bmp_ac1 = twi_readack() << 8 | twi_readack();  // Eeprom Daten
> auslesen 11 x 2 Byte
>  200:  72 df         rcall  .-284      ; 0xe6 <twi_readack>
>  202:        sts  0x0089, r28
>  204:  70 df         rcall  .-288      ; 0xe6 <twi_readack>
>  206:           sts  0x0088, r28

 Und doppelt falsche Werte reingeschrieben.

avr schrieb:
> Darum geht es doch gar nicht. Sondern eher darum, dass jedes mal 6
> Befehle (hier 12 Bytes) verschwendet werden.

 Dann mach doch eine Integer-Funktion daraus.
 Deine (falsche) Assembler-Routine braucht 12 Bytes, Compiler braucht
 12 Bytes mehr, das sind aber nur 6 zusätzliche Takte.
 Und eine Rechenoperation, die man mehr als zweimal benutzt, sollte man
 sowieso als Funktion schreiben.

von (prx) A. K. (prx)


Lesenswert?

Portable Alternative:

uint16_t words[...];
uint8_t *bytes = (uint8_t *)words;

Nach bytes[...] einlesen und als words[...] verwenden.

Geht auch mit einer struct statt words[]. Zur Wahrung der Portabilität 
ist nur erforderlich, dass über einen char-Pointer eingelesen wird, weil 
die eine gewisse Sonderrolle in C haben.

von (prx) A. K. (prx)


Lesenswert?

Timm T. schrieb:
> Ja sorry, daß das C-Implantat bei mir leider vergessen wurde. Ich weiß,
> manche Leute können C schon von Geburt an und müssen nichts mehr lernen.

Nein. Aber mit über 3 Jahrzehnten C im Gepäck und 1-2 teilweise 
geschriebenen C Compilern habe ich einen gewissen Vorsprung. Nur 
solltest du nicht erwarten, den in Nullkommanix einzuholen. ;-)

> Mir kommt es auch nicht auf das letzte Byte an. Mir geht es darum, etwas
> zu lernen.

Eigentlich kam hauptsächlich rüber, dass du deinen Frust irgendwie 
loswerden musstest. Das führt nicht immer zu den hilfreichsten 
Antworten.

von Timm T. (Gast)


Lesenswert?

Also mal der konkrete Fall für den BMP180, kann man ja dann auf andere 
Sachen übertragen. Die Parameter stehen so da:
1
 Calibration coefficients
2
Parameter  MSB  LSB
3
AC1  0xAA  0xAB  signed
4
AC2  0xAC  0xAD  signed
5
AC3  0xAE  0xAF  signed
6
AC4  0xB0  0xB1  unsigned
7
AC5  0xB2  0xB3  unsigned
8
AC6  0xB4  0xB5  unsigned
9
B1  0xB6  0xB7  signed
10
B2  0xB8  0xB9  signed
11
MB  0xBA  0xBB  signed
12
MC  0xBC  0xBD  signed
13
MD  0xBE  0xBF  signed

Ich lese die Bytes von 0xAA bis 0xBF aus und schreibe sie in ein Array. 
Ich greife dann über die Variablen AC1 bis MD auf die Words im Array zu.

Geht dann sowas?
1
union {
2
   uint8_t daten[11 * 2];
3
   uint16_t AC1, AC2, AC3
4
   int16_t  AC4 ...
5
   uint16_t ... MD;
6
}
7
8
daten[i] = wert;  // Schreiben
9
wert = AC1;  // Lesen

von (prx) A. K. (prx)


Lesenswert?

Timm T. schrieb:
> Geht dann sowas?

Die liegen dann alle übereinander, also AC1 über AC2 ... über MD.

Besser, als Prinzip zum selber ausfüllen:
1
union {
2
    uint8_t daten[11 * 2]; 
3
    struct {
4
      uint16_t AC1, AC2, AC3
5
      int16_t  AC4 ...
6
      uint16_t ... MD;
7
    };
8
};

von Timm T. (Gast)


Lesenswert?

A. K. schrieb:
> Besser, als Prinzip zum selber ausfüllen:
>
1
union {
2
>     uint8_t daten[11 * 2];
3
>     struct {
4
>       uint16_t AC1, AC2, AC3
5
>       int16_t  AC4 ...
6
>       uint16_t ... MD;
7
>     };
8
> };

Ah, siehste, das sind die Feinheiten.

Wie spreche ich die dann an? Einfach mit daten[i] und AC1... geht das in 
einer Structure? Muß da nicht immer sowas wie xxx.AC1 stehen?

Bleibt noch das Problem, daß die Daten aus dem BMP180 mit MSB, LSB 
kommen, in uint16 aber LSB, MSB stehen, also lowbyte und highbyte 
vertauscht sind. Sehe ich das richtig?

von (prx) A. K. (prx)


Lesenswert?

Timm T. schrieb:
> Wie spreche ich die dann an? Einfach mit daten[i] und AC1... geht das in
> einer Structure? Muß da nicht immer sowas wie xxx.AC1 stehen?

An der Stelle wär dann doch mal das RTFM fällig. Also der Tipp, es mal 
mit einem C Handbuch zu versuchen. Da steht drin, wie man mit unions und 
structs arbeitet.

> Bleibt noch das Problem, daß die Daten aus dem BMP180 mit MSB, LSB
> kommen, in uint16 aber LSB, MSB stehen, also lowbyte und highbyte
> vertauscht sind. Sehe ich das richtig?

Siehst du richtig, Aber niemand zwingt dich dazu, die in exakt 
sequentieller Reihenfolge abzuspeichern:
   for (uint8_t i = 0; i < N; ++i)
     bytes[i^1] = read_byte();

von Rolf M. (rmagnus)


Lesenswert?

Timm T. schrieb:
> Aber wenn ich dann ne Frage hab: Ist das normal so, oder geht das noch
> besser? werd ich blöd angemacht.

Du wirst nicht böd angemacht, weil du die Frage gestellt hast, sondern 
weil du sie mit den Worten "Blöder Compiler" begonnen und mit "Ist der 
Compiler hier wirklich so blöd?" fortgeführt hast. Wenn man das tut, 
muss man eben auf  entsprechenden Gegenwind gefasst sein.

A. K. schrieb:
> Portable Alternative:
>
> uint16_t words[...];
> uint8_t *bytes = (uint8_t *)words;
>
> Nach bytes[...] einlesen und als words[...] verwenden.

Gefällt mir auch besser als die unions.

von Peter D. (peda)


Lesenswert?

Hier mal mit Union:
1
union b_w {
2
  struct{
3
    uint8_t l;
4
    uint8_t h;
5
  }b;
6
  uint16_t w;
7
};
8
9
uint16_t read_word()
10
{
11
  union b_w val;
12
  val.b.h = read_byte();
13
  val.b.l = read_byte();
14
  return val.w;
15
}
Und compiliert:
1
uint16_t read_word()
2
{
3
 124:  1f 93         push  r17
4
  union b_w val;
5
  val.b.h = read_byte();
6
 126:  0e 94 81 00   call  0x102  ; 0x102 <read_byte>
7
 12a:  18 2f         mov  r17, r24
8
  val.b.l = read_byte();
9
 12c:  0e 94 81 00   call  0x102  ; 0x102 <read_byte>
10
  return val.w;
11
}
12
 130:  91 2f         mov  r25, r17
13
 132:  1f 91         pop  r17
14
 134:  08 95         ret

von Stefan F. (Gast)


Lesenswert?

Ich staune immer bei einfachen ISR, wie viel Code der Compiler da 
erzeugt.

Im Groben und Ganzen bin ich mit dem GCC jedoch zufrieden. In C 
programmieren ist doch wesentlich einfacher, als in Assembler.

Da nehme ich die kleinen Nachteile gerne in Kauf. Auf der Arbeit spielt 
sich das Ganze noch ein Level höher ab, bei Java versus C++. Ich finde 
Java ziemlich doof, aber Geld verdienen kann man damit letztendlich 
trotzdem gut.

von (prx) A. K. (prx)


Lesenswert?

Stefan U. schrieb:
> Ich staune immer bei einfachen ISR, wie viel Code der Compiler da
> erzeugt.

Das liegt nicht zuletzt am Aufwand am Anfang/Ende der ISR. Die AVR 
Architektur ist recht RISC-mässig raumgreifend definiert. Eine 
Akku-Architektur hat es einfacher, weil kaum was gesichert werden muss. 
Und die Cortex M sind an dieser Stelle speziell optimiert worden.

von nicht"Gast" (Gast)


Lesenswert?

Moin,


ein union zu benutzen ist keine gute Idee. Das erzeugt gerne mal 
undefined behaviour (padding bytes und so). Mit uint8_t sollte es 
natürlich funktionieren, aber man muss sich so was ja nicht angewöhnen.

Übrigens:
1
0000006c <main>:
2
{
3
  uint8_t bytes[2];
4
  
5
  uint16_t res = *((uint16_t*)&bytes);
6
    
7
  6c:  80 e0         ldi  r24, 0x00  ; 0
8
  6e:  90 e0         ldi  r25, 0x00  ; 0
9
  70:  08 95         ret
10
11
00000072 <_exit>:
12
  72:  f8 94         cli

Ohne dein lesen vom TWI

von (prx) A. K. (prx)


Lesenswert?

nicht"Gast" schrieb:

Mach es andersrum, wie ich oben skizzierte. char-Pointer dürfen straflos 
alle anderen Daten aliasen.

von Michael B. (laberkopp)


Lesenswert?

Timm T. schrieb:
> union {
>    uint8_t daten[11 * 2];
>    uint16_t AC1, AC2, AC3
>    int16_t  AC4 ...
>    uint16_t ... MD;
> }

Nein, da liegen AC1 und AC4 ja an derselben Speicherstelle.
Lies halt mal im C-Grundlagenbuch was Unions sind.

union
{
   uint8_t daten[11 * 2];
   struct
   {
      uint16_t AC1, AC2, AC3
      int16_t  AC4 ...
      uint16_t ... MD;
   }
   params;
}

von Peter D. (peda)


Lesenswert?

nicht"Gast" schrieb:
> ein union zu benutzen ist keine gute Idee.

Ich sehe es genau andersrum.
Beim Cast weiß ich nicht, wie die Byte-Order ist.
Bei der Union muß ich das nur einmal prüfen und dann ist es eindeutig.

Ich kann sogar die Union für verschiedene Compiler umdefinieren.
Z.B. funktioniert dann die Union auf dem 8051 (C51) und auf dem AVR 
(AVR-GCC). Der Cast würde gewaltig in die Hose gehen.

von Operator S. (smkr)


Lesenswert?

vielleicht wirds ja so klarer, was gemacht wird:
1
struct bpm180
2
{
3
    uint16_t AC1, AC2, AC3
4
    int16_t  AC4 ...
5
    uint16_t ... MD;
6
};
7
8
union
9
{
10
   uint8_t daten[11 * 2];
11
   struct bpm180 params;
12
}

Alle Variabeln, die in einem union stehen, beginnen an der selben 
Adresse.
Wenn du nun ein
uint16_t a
int16_t b
float c

schreibst, greifst du bei allen auf die gleichen 16 Bits zu, werden aber 
verschieden interpretiert.

In deinem Fall teilen sich die Variablen daten und params den gleichen 
Adressbereich. Das Union hilft lediglich dem Programmierer mit dem 
"aliasen", um es logisch lesbarer zu halten.

von (prx) A. K. (prx)


Lesenswert?

Peter D. schrieb:
> Ich sehe es genau andersrum.
> Beim Cast weiß ich nicht, wie die Byte-Order ist.

Ich verstehe nicht was du meinst. Ist doch genau der gleiche Effekt.

Schwierig ist das bei "struct", wenn man darin Bytes und Nicht-Bytes 
mischt. Dann muss man bei den Zugriffen auf die Nicht-Bytes eigentlich 
stets sowas einfliessen lassen, wie bei TCP/IP mit ntohs(). Und hat 
Zirkus mit Alignment.

Beim obigen BMP ist es einfacher, weil nur 16-Bit Worte. Weshalb auf die 
Variante mit "uint16_t words[];" statt struct den Vorteil hat, darauf 
direkt hinzuweisen. Dann kann man mit dem vorhin gezeigten [i^1] die 
Daten passend reinlegen und fertig.

von Peter D. (peda)


Lesenswert?

A. K. schrieb:
> Ich verstehe nicht was du meinst. Ist doch genau der gleiche Effekt.

Nö, ich definiere die Union einmalig je nach Compiler und brauche mich 
fürderhin nicht mehr darum zu kümmern:
1
union b_w {
2
  struct{
3
#ifdef __AVR__
4
    uint8_t l;          // AVR: LSB first
5
    uint8_t h;
6
#else
7
    uint8_t h;          // 8051: MSB first
8
    uint8_t l;
9
#endif
10
  }b;
11
  uint16_t w;
12
};

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

nicht"Gast" schrieb:
> Das erzeugt gerne mal undefined behaviour (padding bytes und so).

Padding ist nicht undefined, sonder implementation-defined (soweit
ich mich erinnere).  Für den AVR wiederum mit seiner 8-bit-Architektur
definiert die Implementierung, dass nie Padding notwendig ist.
Padding ist ja kein Selbstzweck, um künstlich Speicher zu verplempern,
sondern Mittel zum Zweck auf Architekturen, die für bestimmte Daten
eine spezifische Ausrichtung im Speicher benötigen.  Ein 8-Bitter
braucht das nie.

Man kann ansonsten auch Datenstrukturen so definieren, dass sie
Padding-Probleme von vornherein berücksichtigen.  Das Prinzip ist
dabei „vom Großen zum Kleinen“:
1
struct foo {
2
   uint32_t a; // passt immer
3
   uint16_t b; // passt auch ohne Padding
4
   uint8_t c;  // passt ebenfalls
5
   // Hier am Ende wäre auf einer 16- oder 32-Bit-Architektur noch 
6
   // ein Byte Padding nötig, kann man natürlich auch gleich selbst 
7
   // reindefinieren:
8
   uint8_t dummy;
9
};

Dass das Ganze in so einer einfachen Form nicht portabel ist, ist
sonnenklar, aber viele einfache Aufgaben auf einem AVR muss man oft
auch gar nicht portabel implementieren.  Wie A. K. schon schrieb,
treibt man sonst immer gleich einen Rattenschwanz an Mehrarbeit, weil
man nicht nur Padding, sondern auch die Bytereihenfolge berücksichtigen
muss.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Jörg W. schrieb:
> auch gar nicht portabel implementieren.  Wie A. K. schon schrieb,
> treibt man sonst immer gleich einen Rattenschwanz an Mehrarbeit, weil
> man nicht nur Padding, sondern auch die Bytereihenfolge berücksichtigen
> muss.

 Ich denke, peda und nicht A.K. hat das mit Endian geklärt ?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Marc V. schrieb:
> Jörg W. schrieb:
>> auch gar nicht portabel implementieren.  Wie A. K. schon schrieb,
>> treibt man sonst immer gleich einen Rattenschwanz an Mehrarbeit, weil
>> man nicht nur Padding, sondern auch die Bytereihenfolge berücksichtigen
>> muss.
>
>  Ich denke, peda und nicht A.K. hat das mit Endian geklärt ?

Beitrag "Re: Blöder Compiler - Optimieren in C?"

von Bernd K. (prof7bit)


Lesenswert?

Jörg W. schrieb:

> Man kann ansonsten auch Datenstrukturen so definieren, dass sie
> Padding-Probleme von vornherein berücksichtigen.

Genau. Das hat man auch schon in den alten Zeiten gemacht als man solche 
Dinge wie TCP-Header und dergleichen definierte, die sind alle so 
angelegt daß jedes Element auf einem Offset sitzt der durch seine eigene 
Größe teilbar ist und sich somit immer ganz ohne Padding zusammenfügen 
lassen, das war kein Zufall, das war weise Voraussicht.

Wenn ich mir ein Struct ausdenke für irgendwelche Konfigurationsdaten 
lasse ich bei der Deklaration erstmal alle 4 Bytes eine Leerzeile, so 
kann ich ohne viel abzuzählen sofort sehen an welcher Stelle ich noch 
ein int8 oder ein int16 unterbringen kann und wo noch Lücken sind die 
ich mit dummy-bytes auffüllen muss oder durch umsortieren ans Ende 
befördern kann.

> Dass das Ganze in so einer einfachen Form nicht portabel ist,

Das würde ich nicht so dramatisch sehen. Die meisten (alle?) heute und 
auch damals gängigen ABIs verweden für alle Typen ein Alignment das 
genau der Größe des jeweiligen Typs entspricht (self-aligned), also die 
Speicheradresse muss durch die Größe des Typs teilbar sein. So kann man 
ein Struct lückenlos füllen wenn man alles richtig anordnet.

Zur Sicherheit kann man noch durch ein compile-time assert absichern daß 
die Gesamtgröße des structs genau dem entspricht was man erwartet hat 
und wenn man dann in 100 Jahren mal zufällig einen Prozessor begegnet 
bei dem es keine self-aligned typen mehr gibt knallt es sofort beim 
compilieren und man kann sich dann immer noch überlegen wie man das 
anders löst.

von (prx) A. K. (prx)


Lesenswert?

Bernd K. schrieb:
> Genau. Das hat man auch schon in den alten Zeiten gemacht als man solche
> Dinge wie TCP-Header und dergleichen definierte, die sind alle so
> angelegt daß jedes Element auf einem Offset sitzt der durch seine eigene
> Größe teilbar ist und sich somit immer ganz ohne Padding zusammenfügen
> lassen, das war kein Zufall, das war weise Voraussicht.

Nur hatte man diese Voraussicht vermissen lassen, als man TCP/IP in 
Ethernet einpackte. Denn da ist Essig mit dem Alignment vom TCP/IP, wenn 
der Ethernet-Frame aligned ist. Weshalb manche den Ethernet-Frame 
gezielt misalignen, damit TCP/IP wieder passt.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Jörg W. schrieb:
> Beitrag "Re: Blöder Compiler - Optimieren in C?"

 Alignment war damit nicht gemeint.
 Der TO liest Bytes in Big-Endian, schreibt diese aber in
 Little-Endian.
 Das hat peda mit Cast und 'gewaltig in die Hose gehen' gemeint.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Marc V. schrieb:
> Alignment war damit nicht gemeint.

Richtig.  ntohs() (etc.) kümmert sich um die Byteorder.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Jörg W. schrieb:
> Richtig.  ntohs() (etc.) kümmert sich um die Byteorder.

 Bei GCC ?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Marc V. schrieb:
> Jörg W. schrieb:
>> Richtig.  ntohs() (etc.) kümmert sich um die Byteorder.
>
>  Bei GCC ?

Weißt du überhaupt, was noths(), htons(), ntohl() und htonl() sind?

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Jörg W. schrieb:
> Weißt du überhaupt, was noths(), htons(), ntohl() und htonl() sind?

 So ungefähr.
 Nur kann ich die beim GCC nicht finden.
 Besonders noths().

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Marc V. schrieb:
> Nur kann ich die beim GCC nicht finden.

Die stammen ja auch aus dem IP-Stack (welchen auch immer du nimmst).

Der Compiler selbst hat sowas nicht.

Allerdings haben neuere Intel-CPUs, wenn ich mich recht entsinne, das
notwendige Pendant dazu bereits in Hardware, für diese wird der
Compiler sicher ein builtin anbieten.

von (prx) A. K. (prx)


Lesenswert?

Jörg W. schrieb:
> notwendige Pendant dazu bereits in Hardware, für diese wird der
> Compiler sicher ein builtin anbieten.

gcc 4.7.2 interessanterweise nicht (known bug). Für 32 Bits ja, für 16 
Bits nein. Hardware für 16 Bits gibts seit Anbeginn der x86 Ära, für 32 
Bits seit 486.

von (prx) A. K. (prx)


Lesenswert?

Marc V. schrieb:
>  Nur kann ich die beim GCC nicht finden.
>  Besonders noths().

Weil Teil vom Socket API. Mit TCP/IP Lib stehen die Chancen besser.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

A. K. schrieb:
>>  Besonders noths().
>
> Weil Teil vom Socket API. Mit TCP/IP Lib stehen die Chancen besser.

 Es war ein Shreibfehler von Jörg W. und es war nicht ernst gemeint.

 Mit einem einfachem #define my_ntohs(x) swappt der Compiler einfach
 die Bytes, bzw. nimmt zuerst das Byte aus der höherwertigen Adresse
 ohne die ganze bitschieberei die in #define steht.

 Also ist die ganze Diskussion ob der Compiler blöd ist oder nicht,
 überflussig.

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.