Guten abend zusammen:
Ich habe eine Spezielle frage zur Switch Case Verzweigung.
Ich arbeite mit einem Atmega 32 bei 1Mhz internem Takt. Das programm
soll später noch ausgebaut werden.
Und zwar lasse ich den 8bit Timer bei einem Impuls loslaufen, mit
prescaler 256, und wenn dieser Impuls abbricht stoppt der 8bit Timer.
Anschließend gebe ich den Wert an den uint8_t mit dem Namen "zaehler".
Dann möchte ich eine switch case Verzweigung einbauen die Wiefolgt
aussehen soll:
1
switch(zaehler)
2
{
3
case(>=8)&&(<=10):
4
PORTA|=(1<<PA0);
5
break;
6
7
case(>=11)&&(<=13):
8
PORTA|=(1<<PA0);
9
break;
10
}//usw. usw.
Muss ich die Überprüfungen nach case nochmals in eine gesamtklammer
setzen also "case ((>=8) && (<=10));"
oder bin ich mit diesem gedanken total falsch und ich muss alles in else
if schreiben?
Ich wäre um euren Rat sehr dankbar,
Soweit ich das lese, müste da alles so funktionieren. Aber es noch
einmal alles in Klammern zu setzen wäre noch ein Schritt sicherer und
falsch machst du damit sicher nichts.
Also am besten mal drauf schmeissen und probieren.
Danach ist man ja immer schlauer
Und falls es nicht funktioniert werde ich eben ne if-else verzweigung
anwenden.
Danke euch
Jens Freiburger schrieb:> Zumindest mein Compiler übersetzt mir das nun ohne Probleme.
Ja, so wie im Link angegeben funktioniert es natürlich. Was nicht
funktioniert (bzw. sich nicht übersetzen lässt) ist dein erster Versuch
(>=8) && (<=10).
Ja, das habe ich nun am eigenen Leibe erfahren.
Das mit dem h im Zähler habe ich bereits Korrigiert. Danke (nicht zur
Strafe nur zur Übung heisst es ja)
Morgen bau ich die Schaltung dazu auf.
Einen schönen abend euch
A. K. schrieb:> Aber eben nur in GNU-C, nicht in Standard-C.
Leider.
In Standard-C muß man die 65536 Case alle untereinander schreiben.
Und bei 32 oder 64 Bit: no way.
Peter
Reisst ihr mir nun den Kopf runter wenn ich GNU-C nehme anstatt Standard
C?
Bzw.
So lange es funktioniert und ich der alleinige Programmierer bin an dem
Projekt sollte es keine Probleme geben,
oder sehe ich das zu locker?
neiwirger schrieb:> oder sehe ich das zu locker?
Nein, das ist ok. Ich hatte nur extra darauf hingewiesen, weil das im
Kontext vom Thread etwas unterging.
neiwirger schrieb:> Reisst ihr mir nun den Kopf runter wenn ich GNU-C nehme anstatt Standard> C?
Nein, aber wenn du nicht GNU-C nimmst, reißt dir der Compiler den Kopf
runter.
Der Ursprungsposter hat nirgends erwähnt, welchen Compiler er nutzt.
> Bzw.> So lange es funktioniert und ich der alleinige Programmierer bin an dem> Projekt
... und du nicht vorhast, es jemals wo anders hin zu portieren...
> sollte es keine Probleme geben, oder sehe ich das zu locker?
Ich überlege mir normalerweise erst, ob der Vorteil wirklich signifikant
ist. Wenn der Code durch die Verwendung einer GCC-Erweiterung nicht
deutlich besser lesbar, kürzer oder übersichtlicher wird, nutze ich sie
nicht.
neiwirger schrieb:> Reisst ihr mir nun den Kopf runter wenn ich GNU-C nehme anstatt Standard> C?
Nö. Das tut nur der Standard-C-Compiler, wenn Du ihn irgendwann
verwenden musst.
Compilerspezifische Spracherweiterungen mögen in gewissen Situationen
sinnvoll sein, es ist aber ratsam, sie nicht zu verwenden, wenn man
seine Programmierkenntnisse nicht auf ein Inseldasein beschränken
möchte.
Das eingangs genannte switch/case-Konstrukt lässt sich problemlos als
einfache if-Verzweigung ausdrücken.
Und so eine if-Verzweigung dürfte auch jeder C-Compiler nicht minder
performant übersetzen als ein dafür gebasteltes switch/case-Konstrukt.
Rufus Τ. Firefly schrieb:> Das eingangs genannte switch/case-Konstrukt lässt sich problemlos als> einfache if-Verzweigung ausdrücken.
Aber es gibt Beispiele, wo die Lesbarkeit deutlich leidet:
1
switch(x){
2
case0...9:
3
case11...17:
4
case34...39:
5
// mache was
6
break;
7
case30...33:
8
case40...44:
9
case50...55:
10
// mache was anderes
11
break;
12
}
Das ist mit if/else nur schwer zu schreiben.
Rufus Τ. Firefly schrieb:> Und so eine if-Verzweigung dürfte auch jeder C-Compiler nicht minder> performant übersetzen als ein dafür gebasteltes switch/case-Konstrukt.
Beim AVR-GCC nicht.
Beim Switch macht er eine common subexpression elimination, wenn mehrere
Case mit gleichen Passagen enden.
Beim If dagegen nicht.
Peter
Peter Dannegger schrieb:> Aber es gibt Beispiele, wo die Lesbarkeit deutlich leidet:
Da sehe ich dann eher eine algorithmische Herausforderung. Wenn ein
solcher Wertebereich auftritt, würde ich mich fragen, warum der so ...
seltsam aussieht und ob sich das nicht an der Quelle vereinfachen lässt.
Ok.
Ich lasse das in diesem Fall so stehen.
Allerdings notiere ich mir dahinter dass das in GNU geht, in Standard C
nicht.
Sehr interessant das ganze, wenn auch komplex
Rufus Τ. Firefly schrieb:> Wenn ein> solcher Wertebereich auftritt, würde ich mich fragen, warum der so ...> seltsam aussieht
Ich nicht.
Die Anforderungen kommen ja von außen (z.B. ne Statemaschine, ADC-Werte,
Codierung, Verschlüsselung) und ich werde mir nicht die Blöße geben,
dann zu sagen, daß es nicht geht.
Das Programm muß die Aufgabe lösen und nicht umgekehrt.
Auch hat der Switch den Vorteil, daß man überlappende Case um die Ohren
gehauen kriegt.
Überlappende Ifs dagegen nicht und dann sucht man wie blöde an der
falschen Stelle, warum ein hinteres If nicht ausgeführt wird.
Peter
Wenn die einzelnen abzufragenden Intervalle lückenlos zusammenhängend
sind wie im ursprünglichen Beispiel, würde ich immer die If-Methode
bevorzugen (oder, wenn es sehr viele Intervalle sind, eine Kombination
aus Tabelle, Schleife und If-Abfrage).
Also statt diesem
1
switch(z){
2
case8...10:
3
aktion1;
4
break;
5
case11...13:
6
aktion2;
7
break;
8
case14...17:
9
aktion3;
10
break;
11
case18...20:
12
aktion4;
13
break;
14
}
dieses:
1
if(z<8)
2
;
3
elseif(z<11)
4
aktion1;
5
elseif(z<14)
6
aktion2;
7
elseif(z<18)
8
aktion3;
9
elseif(z<21)
10
aktion4;
11
else// (z >=21)
12
;
Die Gründe dafür:
1. Weniger Redundanz im Quelltext: Bei der Case-Methode tauchen die
oberen Intervallgrenzen einer Case-Abfrage um 1 erhöht in der
nächsten Case-Abfrage noch einmal auf. Will man eine der Grenzen
verschieben, muss man das immer an zwei Stellen tun.
2. Bei der If-Methode können leicht Aktionen eingefügt werden für den
Fall, dass der abzufragende Wert kleiner als die unterste Grenze oder
größer als die oberste Grenze ist (s. die leeren Anweisungen (;) im
obigen Beispiel). Bei der Case-Methode bräuchte man eine Notation der
Art
1
case...7:// alle Werte <= 7
2
3
case21...:// alle Werte >= 21
die es aber auch beim GCC nicht gibt. Man kann das zwar auch anders
hintricksen, aber sehr schön sieht das am Ende nicht aus.
3. Das Thema Portabilität wurde ja schon angesprochen.
4. Bei der If-Methode tauchen nur die relevanten Vergleiche im Quellcode
auf. Bei der Case-Methode beinhaltet jeder Case zwei Vergleiche. Der
GCC optimiert das zwar, aber bei der If-Methode braucht man sich
nicht auf die Optimierungsfähigkeiten des Compilers zu verlassen.
5. Trotz Optimierung erzeugt der GCC bei der Case-Methode meist längeren
Code. Wenn man Pech hat, legt er eine Sprungtabelle mit jeweils einem
Sprung für jeden in den Intervallen enthaltenen Zahlenwert an, was
dann trotz weniger Cases viel Programmspeicher beanspruchen kann.
Bei sehr vielen abzufragenden Intervallgrenzen ist eine Sprungtabelle
schneller als die sequentiellen If-Abfragen, bei denen der Rechenaufwand
linear mit der Anzahl der Intervallgrenzen steigt. Wenn die Rechenzeit
aber wirklich ein Problem ist, kann man bei der If-Methode die Abfragen
so anordnen, dass der Aufwand nur logarithmisch steigt.
Es gibt natürlich andere Fälle wie den von Peter gezeigten, wo die
einzelnen Intervalle nicht zusammenhängend sind und deswegen die
Case-Methode vorteilhaft ist. Mir ist so ein Fall aber noch nie
untergekommen.
Peter Dannegger schrieb:> Die Anforderungen kommen ja von außen (z.B.> ne Statemaschine,
Für Zustände (typischerweise symbolisch als Enum definiert) würde ich
nie Bereichsabfragen verwenden, da damit die das Abfrageergebnis von der
Reihenfolge der Symbole im Enum abhängt. Zustände sind — von Ausnahmen
abgesehen — von Natur aus nun einmal nicht totalgeordnet.
> ADC-Werte,
Das kann vielleicht einmal vorkommen, aber typischerweise haben hier die
abzufragenden Bereiche keine Lücken.
> Codierung, Verschlüsselung)
Mag sein, allerdings fällt mir auch hierzu kein sinnvolles Beispiel ein.
Yalu X. schrieb:> 5. Trotz Optimierung erzeugt der GCC bei der Case-Methode meist längeren> Code. Wenn man Pech hat, legt er eine Sprungtabelle mit jeweils einem> Sprung für jeden in den Intervallen enthaltenen Zahlenwert an, was> dann trotz weniger Cases viel Programmspeicher beanspruchen kann.
Der Codegenerator schätzt die Grösse ab und erzeugt den Code
entsprechend.
> Bei sehr vielen abzufragenden Intervallgrenzen ist eine Sprungtabelle> schneller als die sequentiellen If-Abfragen, bei denen der Rechenaufwand> linear mit der Anzahl der Intervallgrenzen steigt.
In nicht-trivialen Fällen wird der Compiler keine sequentiellen Abfragen
produzieren, sondern einen binären Abfragebaum. Damit steigt der Aufwand
nicht linear, sondern logarithmisch.
Wie effizient das hinsichtlich der Laufzeit ist hängt stark davon ab,
wie schnell Sprünge sind und wie gross der Overhead einer Sprungtabelle
ist. Bei AVRs ist der Overhead einer Sprungtabelle sehr gross, bedingte
Sprünge sind jedoch schnell. Bei PC-Prozessoren ist es eher umgekehrt
und die hohe Sprungdichte goutiert auch nicht jeder Prozessor.
> Wenn die Rechenzeit> aber wirklich ein Problem ist, kann man bei der If-Methode die Abfragen> so anordnen, dass der Aufwand nur logarithmisch steigt.
Ebendies macht bereits der Compiler beim switch - aber wohl nur dort.
A. K. schrieb:>> Wenn die Rechenzeit>> aber wirklich ein Problem ist, kann man bei der If-Methode die Abfragen>> so anordnen, dass der Aufwand nur logarithmisch steigt.>> Ebendies macht bereits der Compiler beim switch - aber wohl nur dort.
Danke, das habe ich nicht gewusst, muss ich mal bei Gelegenheit testen.
> Der Codegenerator schätzt die Grösse ab und erzeugt den Code> entsprechend.
Wobei die Betonung auf "schätzt" liegen sollte ;-)
Ein Beispiel:
1
#include<stdint.h>
2
3
volatileuint8_taktion1,aktion2,aktion3,aktion4;
4
5
voidsub1(uint8_tz){
6
switch(z){
7
case12...13:
8
aktion1;
9
break;
10
case14...15:
11
aktion2;
12
break;
13
case16...17:
14
aktion3;
15
break;
16
case18...36:
17
aktion4;
18
break;
19
}
20
}
ergibt bei -Os
1
sub1:
2
ldi r25,lo8(0)
3
mov r30,r24
4
mov r31,r25
5
sbiw r30,12
6
cpi r30,25
7
cpc r31,__zero_reg__
8
brsh .L1
9
subi r30,lo8(-(gs(.L7)))
10
sbci r31,hi8(-(gs(.L7)))
11
ijmp
12
.L7:
13
rjmp .L3
14
rjmp .L3
15
rjmp .L4
16
rjmp .L4
17
rjmp .L5
18
rjmp .L5
19
rjmp .L6
20
rjmp .L6
21
rjmp .L6
22
rjmp .L6
23
rjmp .L6
24
rjmp .L6
25
rjmp .L6
26
rjmp .L6
27
rjmp .L6
28
rjmp .L6
29
rjmp .L6
30
rjmp .L6
31
rjmp .L6
32
rjmp .L6
33
rjmp .L6
34
rjmp .L6
35
rjmp .L6
36
rjmp .L6
37
rjmp .L6
38
.text
39
.L3:
40
lds r24,aktion1
41
ret
42
.L4:
43
lds r24,aktion2
44
ret
45
.L5:
46
lds r24,aktion3
47
ret
48
.L6:
49
lds r24,aktion4
50
.L1:
51
ret
Zum Vergleich:
1
#include<stdint.h>
2
3
volatileuint8_taktion1,aktion2,aktion3,aktion4;
4
5
voidsub2(uint8_tz){
6
if(z<12)
7
;
8
elseif(z<14)
9
aktion1;
10
elseif(z<16)
11
aktion2;
12
elseif(z<18)
13
aktion3;
14
elseif(z<37)
15
aktion4;
16
else// (z >=37)
17
;
18
}
ergibt bei -Os
1
sub2:
2
cpi r24,lo8(12)
3
brlo .L8
4
cpi r24,lo8(14)
5
brsh .L10
6
lds r24,aktion1
7
ret
8
.L10:
9
cpi r24,lo8(16)
10
brsh .L11
11
lds r24,aktion2
12
ret
13
.L11:
14
cpi r24,lo8(18)
15
brsh .L12
16
lds r24,aktion3
17
ret
18
.L12:
19
cpi r24,lo8(37)
20
brsh .L8
21
lds r24,aktion4
22
.L8:
23
ret
Die Case-Variante ist mehr als doppelt so lang und dürfte im Mittel
trotz der Sprungtabelle sogar langsamer sein.
Ich möchte mich nochmal melden da ich mir jetzt den Beitrag nochmals
komplett durchgelesen habe.
Mir ist es wichtig das ich eine einigermaßen genaue Abfrage habe.
Deshalb war mein gedanke die Switch - case Struktur zu nehmen und
dadurch, dank der Verlinkung von perlbastel ziemlich am Anfang des
Themas, den Wert den ich vom Zähler nach meiner Rechnung auf dem Blatt
Papier erhalten soll eingrenzen kann.
1
8...11
Denn der Wert sollte eine Bestimmte zeit weder über, noch
unterschreiten.
Da ich 7 verschiedene Gänge habe, habe ich diese komplett ausgerechnet
(In Bezug auf Prescaler und Signal zeit).
Wenn ich nun aber If - else if nehme, dann habe ich doch immer nur die
abfrage z.B. "kleiner als 8". Somit fällt ein Signal das gerade so die
neun als Zählerstand hat in den nächstgrößeren bereich. Das Signal kann
und darf durchaus etwas Variieren, deshalb die Abfrage zwischen zwei
Werten.
So nebenbei merke ich gerade dass das doch auch mit if möglich sein
sollte:
1
if((zaehler>=8)&&(zaehler<=11))
Das sollte doch die gleiche wirkung haben wie:
1
case8...11
Ich danke euch für das Aufzeigen dieser möglichkeit.
Die Switch Case Variante wollte ich zuerst nehmen, da ich annahm if und
else if in so einem fall zu nehmen wäre vorsichtig ausgedrückt unedel.