Forum: Compiler & IDEs Einstieg in C - komische Fragen


von Timm T. (Gast)


Lesenswert?

Ich programmiere seit Jahren AVR in ASM. Zwischendurch hab ich mal 
Bascom und Luna versucht, bin da aber immer recht schnell wieder 
abgesprungen. Nun soll man ja seine Vorurteile immer mal gegenprüfen, 
also hab ich mit C angefangen. Zum Einstieg hab ich ein in ASM 
begonnenes Projekt nochmal in C aufgebaut. Allerdings macht der Compiler 
mitunter komische Sachen, und ich würde gern verstehen, warum.

Z.B.:
1
  hh = (((Rxtext[2] - '0') & 0x0F) << 4) | ((Rxtext[3] - '0') & 0x0F);
2
     920:  80 91 a5 00   lds  r24, 0x00A5
3
     924:  20 e1         ldi  r18, 0x10  ; 16
4
     926:  82 9f         mul  r24, r18
5
     928:  c0 01         movw  r24, r0
6
     92a:  11 24         eor  r1, r1
7
     92c:  40 91 a6 00   lds  r20, 0x00A6
8
     930:  4f 70         andi  r20, 0x0F  ; 15
9
     932:  48 2b         or  r20, r24

Da soll also aus zwei im Array Rxtext liegenden Chars ein packed BCD 
zusammengesetzt werden, für eine RTC. Wie ich es in ASM gewohnt bin, 
schiebe ich das erste Zeichen um 4 bit und hänge das zweite Zeichen 
dran.

Warum zum Henker will der Compiler hier plutizipieren? In ASM würde man 
ein einfaches SWAP machen und mit 0xF0 maskieren.

Komischerweise macht er in der umgekehrten Operation, also packed BCD zu 
zwei Chars genau das SWAP:
1
  data = ((rtcmm >> 4) & 0x0F) + '0';    // Zehner zu BCD
2
     a6c:  80 91 63 01   lds  r24, 0x0163
3
     a70:  82 95         swap  r24
4
     a72:  8f 70         andi  r24, 0x0F  ; 15
5
  serial_write(data);
6
     a74:  80 5d         subi  r24, 0xD0  ; 208
7
     a76:  0e 94 f9 03   call  0x7f2  ; 0x7f2 <serial_write>
8
  data = (rtcmm & 0x0F) + '0';    // Einer zu BCD
9
     a7a:  80 91 63 01   lds  r24, 0x0163
10
     a7e:  8f 70         andi  r24, 0x0F  ; 15

Warum macht der Compiler das? Ich möchte das gern verstehen.

BTW: Für ASM vs. C Kriege bitte den passenen Thread im Offtopic nutzen.

von Peter II (Gast)


Lesenswert?

hast du die Optimierung eingeschaltet?

von Timm T. (Gast)


Lesenswert?

Optimierung steht aus -Os, nach einer Empfehlung aus dem Tutorial glaub 
ich.

von B. S. (bestucki)


Lesenswert?

Ich kann den Code bestätigen. Der Compiler will wohl in int rechnen und 
warnt auch:
1
warning: conversion to ‘char’ from ‘int’ may alter its value [-Wconversion]

Mit der Option -mint8 wird die Berechnung mit einem swap durchgeführt, 
mit 8 Bit klappts also:
1
  b4:  80 91 03 01   lds  r24, 0x0103
2
  b8:  8f 70         andi  r24, 0x0F  ; 15
3
  ba:  90 91 02 01   lds  r25, 0x0102
4
  be:  92 95         swap  r25
5
  c0:  90 7f         andi  r25, 0xF0  ; 240
6
  c2:  89 2b         or  r24, r25

-mint8 sollte jedoch nicht die Lösung sein. Mit Casts habe ich den 
Compiler auf die Schnelle nicht dazu bewegen können, dies so zu tun.

von Sir Adam Weishaupt (Gast)


Lesenswert?

Timm T. schrieb:
> Warum zum Henker will der Compiler hier plutizipieren?

Es gibt tatsächlich noch Dinge die mir bisher nicht untergekommen sind.

von Peter II (Gast)


Lesenswert?

be s. schrieb:
> warning: conversion to ‘char’ from ‘int’ may alter its value
> [-Wconversion]

hast du Rxtext als char definiert? Was passiert bei unsigned char?

von Timm T. (Gast)


Lesenswert?

Ja sorry, war klar das wieder Info fehlt: Das Array ist als char und hh 
bzw. data sind als uint8_t definiert. Und bei mir gibt es keine 
Compilerwarnung.

von Peter II (Gast)


Lesenswert?

Timm T. schrieb:
> Das Array ist als char

char ist blöd. Denn es kann auch signed sein, dann ist das shift 
undefiniert.

von Peter II (Gast)


Lesenswert?

Peter II schrieb:
> char ist blöd. Denn es kann auch signed sein, dann ist das shift
> undefiniert.

vergiss es, du rechnest ja mit -'0' - char ist also ok

von B. S. (bestucki)


Lesenswert?

Peter II schrieb:
> hast du Rxtext als char definiert?

Alles char.

Peter II schrieb:
> Was passiert bei unsigned char?

Gleiches Ergebnis.

Sogar das Casten aller Ausdrücke ändert nichts am Ergebnis:
1
char hh = (char)((char)((char)((char)((char)Rxtext[2] - (char)'0') & (char)0x0F) << (char)4) | (char)((char)((char)Rxtext[3] - (char)'0') & (char)0x0F));

von (prx) A. K. (prx)


Lesenswert?

Timm T. schrieb:
> Warum zum Henker will der Compiler hier plutizipieren? In ASM würde man
> ein einfaches SWAP machen und mit 0xF0 maskieren.
>
> Komischerweise macht er in der umgekehrten Operation, also packed BCD zu
> zwei Chars genau das SWAP:

In C gilt bekanntlich, dass eine Berechnung mindestens in "int" zu 
erfolgen hat, oder zumindest das Ergebnis dem entsprechen sollte. Da << 
auf 8 Bits nicht in den 8 Bits bleibt ist - lokal auf diese Operation 
bezogen - eine 8-Bit Rechnung nicht zulässig. Tatsächlich ist diese 
Multiplikation der effizienteste Weg, genau dies zu tun.

Das geht erst durch die (vermutlich) anschliessende Zuweisung an eine 
8-Bit Variable wieder nach hinten los, aus der er schliessen könnte, 
dass es bei den 8 Bits bleibt.

von B. S. (bestucki)


Lesenswert?

Peter II schrieb:
> char ist blöd. Denn es kann auch signed sein, dann ist das shift
> undefiniert.

Das wars! char wie auch unsigned char wird als int gerechnet, daher 
multipliziert der Compiler. Castet man die Operanden nach unsigned int, 
klappts auch mit dem swap.

von Peter II (Gast)


Lesenswert?

A. K. schrieb:
> In C gilt bekanntlich, dass eine Berechnung mindestens in "int" zu
> erfolgen hat, oder zumindest das Ergebnis dem entsprechen sollte. Da <<
> auf 8 Bits nicht in den 8 Bits bleibt ist - lokal auf diese Operation
> bezogen - eine 8-Bit Rechnung nicht zulässig.

da er vorher aber die obere 4bits abschneidet geht es NIE über 8bin 
hinaus. Damit kommt bei 8bit das gleiche wie bei 16bit raus. Damit 
dürfte er es optimieren.

von B. S. (bestucki)


Lesenswert?

Nachtrag:

Hier noch den Code:
1
unsigned char hh = ((((Rxtext[2] - '0') & 0x0FU) << 4) | ((Rxtext[3] - '0') & 0x0FU));

von (prx) A. K. (prx)


Lesenswert?

Übersetzt das mal für einen AVR, der keinen Multiplier hat. Da kommt 
genau das raus, was man erwartet. Schätze dass da eine von Johanns 
wohlmeinenden Optimierungen einen Fall erwischt hat, in dem sie nach 
hinten losgeht. Denn eigentlich ist die Multiplikation um Längen besser 
als ein 16-Bit Shift.

: Bearbeitet durch User
von Peter II (Gast)


Lesenswert?

A. K. schrieb:
> Die Grenzen der Erkenntnis... Er könnte das auf den Wertebereich prüfen,
> tut es aber offensichtlich nicht. Die MUL-Version ist zunächst einmal um
> Längen besser als eine normale 16-Bit Version. Das mag insgesamt gesehen
> wichtiger sein als dieser Fall, wo es etwas teurer ist.

scheinbar ist die Ursache aber mehr das das Array signed ist. Und das 
shift ist bei signed nicht mehr definiert.
1
hh = ((( (unsigned char)(Rxtext[2] - '0')) & 0x0F) << 4) | (( (unsigned char)(Rxtext[3] - '0')) & 0x0F);
müsste da helfen

von (prx) A. K. (prx)


Lesenswert?

Peter II schrieb:
> scheinbar ist die Ursache aber mehr das das Array signed ist. Und das
> shift ist bei signed nicht mehr definiert.

Da ((a - b) & 0x0F) und (((a - b) & 0x0F) << 4) unabhängig von den Typen 
von a und b immer positiv sind, spielen Vorzeichen für das Ergebnis der 
Rechnung keine Rolle. Aber es kann den Compiler beim Test auf bestimmte 
zu optimierende Muster behindern.

Oft ist es ja andersrum: Eine undefinierte Operation behindert den 
Compiler weniger als eine definierte. Da sind im Laufe der Entwicklung 
von GCC schon manche Kinnladen runtergefallen.

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Das Beispiel lässt sich so nicht übersetzen, und Rumraten hat sich als 
eine der aufwändigsten und ineffektivsten Werkzeuge zur 
Informationsbeschaffung...

Insbersondere wird bei mit[tm] folgender Code erzeugt, d.g. wenn ich so 
lange rumrate, bis ich compilierbaren Code erhalte:
1
  movw r30,r24
2
  ldd r25,Z+2
3
  swap r25
4
  andi r25,lo8(-16)
5
  ldd r24,Z+3
6
  andi r24,lo8(15)
7
  or r24,r25

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

...du hast überigens ganze 12 Operationen in die eine Zeile gequetscht. 
Wesentlich übersichtlicher wird das z.B. so:
1
#include <stdint.h>
2
3
static uint8_t get_digit (char c)
4
{
5
    return (c - '0') & 0xf;
6
}
7
8
uint8_t calc (char t[])
9
{
10
    return (get_digit (t[2]) << 4) | get_digit (t[3]);
11
}

von Peter II (Gast)


Lesenswert?

A. K. schrieb:
> Da ((a - b) & 0x0F) unabhängig von den Typen von a und b immer positiv

so ganz sicher bin ich mir da nicht, ob das wirklich definiert ist

https://www.securecoding.cert.org/confluence/display/c/INT13-C.+Use+bitwise+operators+only+on+unsigned+operands

von (prx) A. K. (prx)


Lesenswert?

Johann L. schrieb:
> Das Beispiel lässt sich so nicht übersetzen,
1
unsigned char f(unsigned char a, unsigned char b)
2
{
3
         return (((a - '0') & 0x0F) << 4) | ((b - '0') & 0x0F);
4
}

von (prx) A. K. (prx)


Lesenswert?

Peter II schrieb:
> so ganz sicher bin ich mir da nicht, ob das wirklich definiert ist

Das ist hier unwichtig. Wenn eine Operation für bestimmte Werte 
undefiniert ist, dann muss der Compiler diese Fälle überhaupt nicht für 
das Ergebnis berücksichtigen und darf auch Code erzeugen, bei dem dir 
bei solchen Werten der Weihnachtsbaum abbrennt.

von Timm T. (Gast)


Lesenswert?

A. K. schrieb:
> Eine undefinierte Operation behindert den
> Compiler weniger als eine definierte.

Und wie schreibe ich es dann, so daß der Compiler macht was ich will, 
und das möglichst effektiv?

Andere Aufgabe, wieder ein Shift um 4, der Compiler ist sehr 
einfallsreich:
1
     7e6:  cf ef         ldi  r28, 0xFF  ; 255
2
     7e8:  df ef         ldi  r29, 0xFF  ; 255  
3
...
4
data = ((~i << 4) & 0xF0) | i;  // Kanal invertiert in high Nibble, normal in low Nibble
5
     7f4:  9e 01         movw  r18, r28
6
     7f6:  84 e0         ldi  r24, 0x04  ; 4
7
     7f8:  22 0f         add  r18, r18
8
     7fa:  33 1f         adc  r19, r19
9
     7fc:  8a 95         dec  r24
10
     7fe:  e1 f7         brne  .-8        ; 0x7f8 
11
     800:  8c 2f         mov  r24, r28
12
     802:  80 95         com  r24
13
     804:  82 2b         or  r24, r18

Das in eine Schleife zu packen scheint mir jetzt nicht optimal.

i und data sind uint_8.

In ASM sieht das elegant so aus:
1
  mov  TEMP8, TEMP1
2
  com  TEMP8      ;Kanal invertieren
3
  swap  TEMP8      ;Kanal in high Nibble
4
  andi  TEMP8, 0xF0    ;low Nibble löschen
5
  or  TEMP8, TEMP1    ;Kanal normal in low Nibble

von B. S. (bestucki)


Lesenswert?

Timm T. schrieb:
> In ASM sieht das elegant so aus:

So?
1
  b8:  89 2f         mov  r24, r25
2
  ba:  80 95         com  r24
3
  bc:  82 95         swap  r24
4
  be:  80 7f         andi  r24, 0xF0  ; 240
5
  c0:  89 2b         or  r24, r25


Das Problem ist, dass der Compiler in int rechnen will. Zwingt man in, 
in unsigned int zu rechnen, wirds auch wie gewünscht umgesetzt.
1
data = (((unsigned int)~i << 4) & 0xF0) | i;

von (prx) A. K. (prx)


Lesenswert?

Timm T. schrieb:
> Und wie schreibe ich es dann, so daß der Compiler macht was ich will,
> und das möglichst effektiv?
>
> data = ((~i << 4) & 0xF0) | i;  // Kanal invertiert in high Nibble,

data = ((uint8_t)(~i << 4) & 0xF0) | i;

von Timm T. (Gast)


Lesenswert?

Und jetzt der Witz, verschiebe ich die Negation um eine Stelle aus der 
Klammer raus, wird aus obigem Code mit Schleife:
1
data = (~(i << 4) & 0xF0) | i;    // Kanal invertiert in high Nibble, normal in low Nibble
2
     7f0:  8c 2f         mov  r24, r28
3
     7f2:  82 95         swap  r24
4
     7f4:  80 7f         andi  r24, 0xF0  ; 240
5
     7f6:  80 95         com  r24
6
     7f8:  80 7f         andi  r24, 0xF0  ; 240
7
     7fa:  8c 2b         or  r24, r28

Was bis auf das eine überflüssige andi schon ziemlich der optimierten 
ASM Lösung entspricht.

Nur weil ich aus (~i << 4) ein ~(i << 4) gemacht habe?

Muß ich das verstehen?

von Timm T. (Gast)


Lesenswert?

Johann L. schrieb:
> ...du hast überigens ganze 12 Operationen in die eine Zeile gequetscht.

Nun dachte ich gerade ein Vorteil von C wäre, daß ich Funktionen 
zusammenfassen kann und nicht jede Operation einzeln abhandeln muß.

von (prx) A. K. (prx)


Lesenswert?

Timm T. schrieb:
> Muß ich das verstehen?

Letztlich sind etliche Optimierungen grob gesehen eine Art 
Mustererkennung. Es gibt allerlei Muster, die im Zwischencode gesucht 
werden, und je nach Phase entweder durch anderen Zwischencode ersetzt 
werden, oder darauf spezialisierten Code der Zielmaschine erzeugen.

Diese Muster sind so wenig perfekt wie ihre Autoren und scheitern in 
manchen Fällen, in denen sie eigentlich auch anwendbar wären. Oder 
werden angewandt, obwohl sie zur Verschlechterung führen. Wichtiger ist, 
dass man auf der richtigen Seite irrt. Also lieber 2 Takte schlechter 
liegt als manchmal falschen Code erzeugt.

Wenn man das genau verstehen will, dann muss man sich auf die Ebene 
begeben, in der solcher Code im Compiler dargestellt wird. Und wie darin 
Optimierungsmöglichkeiten erkannt werden.

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Timm T. schrieb:
> Johann L. schrieb:
>> ...du hast überigens ganze 12 Operationen in die eine Zeile gequetscht.
>
> Nun dachte ich gerade ein Vorteil von C wäre, daß ich Funktionen
> zusammenfassen kann und nicht jede Operation einzeln abhandeln muß.

Der Vorteil von C ist nicht, anstatt vertikaler Spaghettis (Assembler) 
horizontale Spaghettis (ewig lang Ausdrücke) zu nutzen.

Die von mir vorgeschlagene Hilfsfunktion vermeidet zum einen Redundanz, 
nämlich das doppelte (x - '0') & 0xf und zum anderen bewirkt sie, dass 
das (Zwischen)Ergebnis nur 8-Bit hat.  Bei deinen Termen wird immer 
wieder implizit auf int erweitert, und was fürhrt dazu, dass schleißlich 
mit 16 Bits gerechnet wird.  Erst bei der Zuweisung wird wieder auf 8 
Bit reduziert.  Außerdem ist mit unsigned i.d.R. angenehmer zu rechnen 
als mit signed.

Übrigens ist ~(i << 4) was komplett anderes als ~i << 4.  Im ersten Fall 
wird sign-extended auf 16 bit, geschoben und dann begiert.  Im zweiten 
Fall wird sign-extended, negiert, und dann geschoben.

Zu bedenken ist auch, dass der Compiler bestimmte Ausdrücke optimiert, 
dass er aber — von wenigen Ausnahmen abgesehen — für jede Variable und 
für jeden Term Buchführung betreibt, welche Bits gesetzt, welche nicht 
gesetzt, welche unbenutzt und welche unter den Tisch fallen können, ohne 
einen der anhängigen Ausdrücke zu ändern.

von Timm T. (Gast)


Lesenswert?

Johann L. schrieb:
> Übrigens ist ~(i << 4) was komplett anderes als ~i << 4.

Das ist mir schon klar, aber durch das Begrenzen auf das obere Nibble 
kommt es für mich auf das selbe Ergebnis raus.

Johann L. schrieb:
> Bei deinen Termen wird immer
> wieder implizit auf int erweitert, und was fürhrt dazu, dass schleißlich
> mit 16 Bits gerechnet wird.  Erst bei der Zuweisung wird wieder auf 8
> Bit reduziert.

Ja sorry, ich habe vor einer Woche mit C angefangen und in dem Projekt 
jetzt 8k Flash vollgemüllt. Ich bin da noch nicht sooo erfahren drin...

Deswegen stell ich doch die Fragen: Weil sowas eben nicht im Tutorial 
steht.

von Peter II (Gast)


Lesenswert?

Timm T. schrieb:
> Ja sorry, ich habe vor einer Woche mit C angefangen und in dem Projekt
> jetzt 8k Flash vollgemüllt. Ich bin da noch nicht sooo erfahren drin...

die Frage ist ob es wirklich ein den paar Zeilen zusätzlich liegt. Da 
wird wohl noch ein größere Problem vorhanden sein. Nicht das irgendwo 
float dazugekommen ist.

von msx (Gast)


Lesenswert?

Peter II schrieb:
> Da
> wird wohl noch ein größere Problem vorhanden sein. Nicht das irgendwo
> float dazugekommen ist.

Nicht stänkern! Die float Grundrechenarten brauchen nur etwa 1kB Code.

Timm T. schrieb:
> Ja sorry, ich habe vor einer Woche mit C angefangen und in dem Projekt
> jetzt 8k Flash vollgemüllt. Ich bin da noch nicht sooo erfahren drin...

Das ist Alles nicht so wild. Sieh Dir den erzeugten Code an, dann kannst 
Du im Zweifelsfall erkennen, welche Konstrukte effektiven Code erzeugen. 
Ob da jetzt ein mul oder ein swap verwendet wird, wird Dir irgendwann 
völlig egal sein.

von Peter II (Gast)


Lesenswert?

msx schrieb:
> Nicht stänkern! Die float Grundrechenarten brauchen nur etwa 1kB Code.

was immerhin mehr als 10% von 8k sind. Und damit ist das ein recht 
großer brocken!

von msx (Gast)


Lesenswert?

Peter II schrieb:
> was immerhin mehr als 10% von 8k sind. Und damit ist das ein recht
> großer brocken!

Blödsinn!
Wenn man es braucht, dann braucht man es. Und wenn float nicht reicht, 
dann rechnet man eben double. Wo ist das Problem?

von Peter II (Gast)


Lesenswert?

msx schrieb:
> Peter II schrieb:
>> was immerhin mehr als 10% von 8k sind. Und damit ist das ein recht
>> großer brocken!
>
> Blödsinn!

warum ist die Rechnung Blödsinn?

> Wenn man es braucht, dann braucht man es.
richtig, aber wenn man es nicht braucht und es nur aus "Unwissenheit" 
reingemacht hat kann man es sparen

> Und wenn float nicht reicht,
> dann rechnet man eben double.
double ist identisch zu float bringt als gar nichts.


> Wo ist das Problem?
es braucht Speicher und diese möchte  Timm Thaler sparen.

von Stefan K. (stefan64)


Lesenswert?

Wenn Du gerne alles in 8-Bit rechnen willst und verwirrt bist, wenn der 
Compiler auf 16-Bit erweitert, dann benutze doch einfach die Option 
-mint8.

Allerdings kannst Du dann einige der Library-Funktionen nicht mehr 
benutzen. Nicht umsonst wird vor -mint8 hier oft gewarnt.

Gruß, Stefan

von msx (Gast)


Lesenswert?

Peter II schrieb:
> double ist identisch zu float bringt als gar nichts.

Na gut, dann müssen wir nicht weiterreden.

von Peter II (Gast)


Lesenswert?

msx schrieb:
>> double ist identisch zu float bringt als gar nichts.
>
> Na gut, dann müssen wir nicht weiterreden.

aktuell geht es um AVR

https://gcc.gnu.org/wiki/avr-gcc

und das ist das nun mal so.

von Oliver S. (oliverso)


Lesenswert?

Timm T. schrieb:
> Allerdings macht der Compiler
> mitunter komische Sachen, und ich würde gern verstehen, warum.

Auch wenn das einen gelernten Assemblerprogrammierer natürlich brennend 
interessiert, ist es die falsche Frage. Ein Compiler, egal, welcher, 
macht immer, WAS du willst, aber er macht es selten so, WIE du willst. 
(Wobei die richtige Formulierung des WAS nicht immer trivial einfach 
ist).

Lass den Compiler machen, was er will. Ab und zu in den erzeugten 
Assembler-Code zu schauen, ist ok, aber jede einzelne Codezeile auf die 
Goldwaage zu legen wird dich nicht weiterbringen.

Oliver

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Timm T. schrieb:
> ich habe vor einer Woche mit C angefangen

Kein Problem.  Wenn die ganzen erfahrenen Programmierer, die sich 
momentan von Moby am Nasenring durch diverse Beiträge ziehen lassen, dir 
stattdessen hier beim C-Einstieg helfen, bist du in 1 Woche ein 
ausgewiesener C-Experte.

von msx (Gast)


Lesenswert?

Johann L. schrieb:
> von Moby am Nasenring

Das gefällt mir!
Wenn Moby kommt, ist es Zeit zu gehen.

von Timm T. (Gast)


Lesenswert?

Oliver S. schrieb:
> Lass den Compiler machen, was er will. Ab und zu in den erzeugten
> Assembler-Code zu schauen, ist ok, aber jede einzelne Codezeile auf die
> Goldwaage zu legen wird dich nicht weiterbringen.

Es geht mir darum, gleich "richtig" C für AVR anzuwenden, so daß zum 
Bleistift solche Sachen wie unnötige float-Einbindung nicht vorkommen - 
und nein, es werden keine float eingebunden.

Moby A. schrieb im Beitrag #4390723:
> Ist doch nicht tragisch. Denn
...
> ... immer noch fürn Tiny Projekt einen Cortex einspannen ;-)

Du mußt jetzt sehr tapfer sein: Ich hab das vorher in ASM geschrieben, 
und ich würde behaupten, ich kann ASM, denn ich mach das 15 Jahre und 
auch für größere Projekte.

Der vorherige ASM war auch 5k groß, und inzwischen sind noch einige 
Lock-up-Tables und Menustrings - die bekanntlich ordentlich Speicher 
fressen - dazugekommen.

So viel schlechter schneidet der C-Compiler gegenüber meinem ASM Code 
nicht ab. Er macht nur manchmal Sachen, die ich anders lösen würde.

Und ich möchte bitte hier einen ASM gegen C Krieg. Ich möchte verstehen, 
wie C umgesetzt wird, um es möglichst optimal zu programmieren.

von (prx) A. K. (prx)


Lesenswert?

Timm T. schrieb:
> Und ich möchte bitte hier einen ASM gegen C Krieg.

Hmm. Könntest du ein "k" brauchen? ;-)

von Oliver S. (oliverso)


Lesenswert?

Timm T. schrieb:
> Es geht mir darum, gleich "richtig" C für AVR anzuwenden, so daß zum
> Bleistift solche Sachen wie unnötige float-Einbindung nicht vorkommen -

Nun ja, dein Ausgangsbeitrag liest sich anders, da geht es um 
Unterschiede in der Größenordnung einzelner Bytes. Und das ist zwar für 
Compilerbauer interessant, zum lernen von C an sich aber völlig unnötig, 
und auch der völlig falsche Weg.

Oliver

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


Lesenswert?

Timm T. schrieb:
> so daß zum Bleistift solche Sachen wie unnötige float-Einbindung nicht
> vorkommen

float tritt man sich nicht nur „aus Versehen“ ein, das benutzt man
absichtlich.  Es ist eher eine Falle zu vergessen, dass
1
float q = 3 / 24;

eben nicht 0.125 ergibt sondern 0, weil die Division noch im
Ganzzahlbereich erledigt wird.

Allerdings wirst du als Umsteiger, wenn du einmal drauf gekommen bist,
dass die Gleitkommazahlen weder sofort um sich schießen noch dich zum
sofortigen Kauf eines ATmega2560 nötigen, bei Bedarf schnell merken,
dass sie zuweilen mehr als bequem sein können. ;-)  Die Genauigkeit
der 6 … 7 Dezimalstellen genügt an vielen Stellen, und während man
sich bei Festkommarechnungen eben immer um den Dynamikbereich
kümmern muss und darum, dass dessen Grenzen nie versehentlich mal
in der Praxis überschritten werden können, ist der Dynamikbereich von
Gleitkommazahlen in erster Näherung beliebig groß. :)  Zwar gibt's für
den AVR keine 64-bit-double (nichtmal 48-bit wie bei Turbo Pascal),
aber die 32-bit-Operationen sind verdammt kompakt implementiert.
Ich möchte meine nicht vorhandene Perücke verwetten, dass sie in der
erreichbaren Codegröße manch eine handgefeilte
Festkomma-32-Bit-Implementierung in den Sack stecken.

: Bearbeitet durch Moderator
von msx (Gast)


Lesenswert?

Jörg W. schrieb:
> Zwar gibt's für
> den AVR keine 64-bit-double

Gibt es doch, man muß sie nur nehmen: IAR-Kickstart für AVR bietet 
double. Daß AVR-GCC es nicht kann, bedeutet doch nicht, daß es nicht 
ginge oder geht.

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


Lesenswert?

msx schrieb:
> Gibt es doch, man muß sie nur nehmen: IAR-Kickstart für AVR bietet
> double.

Wie weit kommt man denn mit einer amputierten Version für gerade mal
4 KiB Code bei 64-Bit-double-Berechnungen?

Ich denke nicht, dass das eine wirklich sinnvolle Option ist.  IAR
ist ein guter Compiler, aber die Kickstart-Version nun gerade wegen
64-bit-double zu empfehlen, klingt mir nicht wirklich plausibel (und
die Bezahlversion ist halt schweineteuer).

: Bearbeitet durch Moderator
von msx (Gast)


Lesenswert?

Jörg W. schrieb:
> Wie weit kommt man denn mit einer amputierten Version für gerade mal
> 4 KiB Code bei 64-Bit-double-Berechnungen?

Es auszuprobieren kostet kein Geld, und wenn die reinen Berechnungen im 
Vordergrund stehen, kommt man damit auch weit - wo auch immer das sein 
mag.
Mit Vorurteilen "geht nicht, gibt's nicht" kommt man jedoch nicht einmal 
über den Tellerrand.

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


Lesenswert?

msx schrieb:
> Es auszuprobieren kostet kein Geld, und wenn die reinen Berechnungen im
> Vordergrund stehen, kommt man damit auch weit

Und dann, als Math-Koprozessor benutzen, und den Rest auf einer
anderen CPU mit einem anderen Compiler arbeiten lassen?

Ich mein', Daten sammeln und darstellen muss man ja im Allgemeinen
schon noch, die Rechnung ist dann nur das Mittel zum Zweck.

> Es auszuprobieren kostet kein Geld

Zeit ist Geld, außerdem müsste man dafür erst noch ein Windows 
ausbuddeln.

von Timm T. (Gast)


Lesenswert?

Jörg W. schrieb:
> Allerdings wirst du als Umsteiger, wenn du einmal drauf gekommen bist,
> dass die Gleitkommazahlen weder sofort um sich schießen noch dich zum
> sofortigen Kauf eines ATmega2560 nötigen, bei Bedarf schnell merken,
> dass sie zuweilen mehr als bequem sein können.

Das mag schon sein, ich hab eine PID Motorregelung mit Zielführung 
programmiert, da passte zwar alles in längstens 24 oder 32 Bit und 
selbst die Wurzel aus einer 32 Bit Zahl war kein Hexenwerk. Allerdings 
wäre es in C und mit Floats vielleicht entspannter gewesen. Vielleicht 
aber auch zu langsam.

Oliver S. schrieb:
> dein Ausgangsbeitrag liest sich anders, da geht es um
> Unterschiede in der Größenordnung einzelner Bytes.

Nunja, mir kommt es eigentlich nicht auf ein paar Bytes mehr oder 
weniger an. Mir ist das nur an diesem überschaubaren Beispiel 
aufgefallen.

Daß allerdings der Compiler in meiner ADC-Sample Routine, in der 24 ADC 
Kanäle über I2C eingelesen und in ein Array geschrieben werden, erstmal 
12 Register pushen und am Ende wieder poppen will, scheint mir doch noch 
etwas optimierungsbedürftig. In ASM brauch ich dafür grad mal 4 Register 
und den Y-Pointer.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Timm T. schrieb:
> ich hab eine PID Motorregelung mit Zielführung programmiert,
> da passte zwar alles in längstens 24 oder 32 Bit und selbst die
> Wurzel aus einer 32 Bit Zahl war kein Hexenwerk. Allerdings
> wäre es in C und mit Floats vielleicht entspannter gewesen.
> Vielleicht aber auch zu langsam.

Einzelne neuralgische Routinen kannst du auch mit C verwenden sofern sie 
dem ABI genügen: http://gcc.gnu.org/wiki/avr-gcc#Register_Layout

Beispiele für 32-Bit Wurzel (Ganzzahl): AVR Arithmetik: Wurzel

> Daß allerdings der Compiler in meiner ADC-Sample Routine, in der 24 ADC
> Kanäle über I2C eingelesen und in ein Array geschrieben werden, erstmal
> 12 Register pushen und am Ende wieder poppen will, scheint mir doch noch
> etwas optimierungsbedürftig. In ASM brauch ich dafür grad mal 4 Register
> und den Y-Pointer.

Compiler funktionieren anders als Menschen...  Wenn es ein 
compilierbares Beispiel dazu gibt kann ich eine Stellugnahme dazu 
abgeben.

von msx (Gast)


Lesenswert?

Timm T. schrieb:
> und am Ende wieder poppen will, scheint mir doch noch
> etwas optimierungsbedürftig. In ASM brauch ich dafür grad mal 4 Register
> und den Y-Pointer.

... um nur den markanten Teil zu zitieren ;-)
In ASM wirst Du immer kürzeren Code schreiben können, nur zu welchem 
Preis? Nimm eine 16 Bit Variable, oder besser noch ein Array davon, die 
Du auf 32 Bit umstellen mußt. In C änderst Du an einer Stelle int16_t 
nach int32_t.
In ASM mußt Du alle Stellen ändern, wo diese Variable angesprochen wird. 
Viel Spaß bei der Fehlersuche.
Jörg W. schrieb:
> Zeit ist Geld


Es ist kein Problem, in C auch ASM-Routinen aufzurufen. Das ist bei 
zeitkritischen ISRs manchmal notwendig. ASM kannst Du ja schon ;-)
Wenn der Compiler es zuläßt (oben genannte Kickstart-Version) kann man 
sich für die ASM-Routinen auch Register reservieren, die nicht bei jedem 
Aufruf gesichert werden müssen. Noch besser: dafür braucht man nicht 
einmal Linux zu installieren.

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.