Forum: Mikrocontroller und Digitale Elektronik Mehrfachabfrage mit if und else oder besser?


von Achim S. (achims)


Lesenswert?

Hallo
in einem Programm muss ich a ca. 10 mal abfragen auf wahr oder falsch.
Habe es bisher so gemacht:
1
if (a==1)
2
  { dann mach nr 1;}
3
4
if (a==2)
5
  { dann mach nr 2;}
6
7
if (a==3)
8
  { dann mach nr 3;}
9
  
10
if (a==4)
11
  { dann mach nr 4;}
12
....
Eine andere Möglichkeit ist:
1
if (a==1)
2
  { dann mach nr 1;}
3
    else if (a==2)
4
      { dann mach nr 2;}
5
    else if (a==3)
6
      { dann mach nr 3;}
7
    else if (a==4)
8
      { dann mach nr 4;}
9
....
Bei 10 Abfragen wird das ganz schön lang. Wahrscheinlich muss ich das 
auf 16 Abfragen verlängern. Wird noch länger.
Wie kann ich das ganze einfacher gestalten?
a wird zwischen 0 und 64 liegen.
C, 16MHz, At128
achim

: Verschoben durch User
von Tom W. (nericoh)


Lesenswert?

switch(a)
{
  case 1:
    mach nr 1;
  break;

  case 2:
    mach nr 2;
  break;

  ...

}

von dirk h. (Gast)


Lesenswert?

vil. ein switch-case

von Achim S. (achims)


Lesenswert?

ist switch-case Sinnvoll wenn a bei jeder Abfrage immer unterschiedlich 
sein kann?
a verändert sich bei jeder Eingabe, ist nicht konstant.

von Mathias M. (matjes)


Lesenswert?

switch/case entspricht exakt dem "else if" Konstrukt. Wenn "dann mach nr 
1" auch a verändert, dann sind deine beiden geposteten Konstrukte 
verschieden.

von Jakob (Gast)


Lesenswert?

Sobald du mit if () einen bestimmten Wert von a erkannt und
behandelt hast, solltest du die Abfrage-Liste (mit dem
viel-gefürchteten GOTO) verlassen, um für einen neuen Wert
von a bereit zu sein.
Alles andere führt zu Unübersichtlichkeit, oder Fehlern...

Bei If  else if  else, oder switch  case  break / default
ist das eben (ohne böses GOTO) schon im Code eingebaut.

Verkürzen könnte man das Ganze nur, wenn Redundanzen vorliegen,
also der Wert von a in Gruppen zusammengefasst werden kann:

if ( a > 7 && a < 19 )
....

von Rene H. (Gast)


Lesenswert?

Mathias M. schrieb:
> switch/case entspricht exakt dem "else if" Konstrukt. Wenn "dann
> mach nr 1" auch a verändert, dann sind deine beiden geposteten
> Konstrukte verschieden.

Nicht ganz. Ein switch/case Konstrukt entspricht nur dem else if wenn 
das "break" in jedem case vorhanden ist.
Der Kompiler macht aber aus switch/case/break das selbe wie if/else.

von Jakob (Gast)


Lesenswert?

Rene H. schrieb:
> Der Kompiler macht aber aus switch/case/break das selbe wie if/else.

Was soll der Compiler denn auch anderes machen?
So ein µC/µP hat Vergleichsbefehle und (bedingte) Sprungbefehle.

Und wenn das Gleiche passieren soll, muss (optimiert) auch der
gleiche Code rauskommen.

von H.Joachim S. (crazyhorse)


Lesenswert?

Bei grösseren Konstrukten kann die Laufzeit schon ein Problem werden. Es 
wird eben von oben nach unten stur verglichen. Wenn das stört, kann man 
eine Vorauswahl treffen und die Sache auf mehrere switch/case-Blöcke 
aufteilen.

In Assembler hätte man da deutlich schnellere Alternativen mit einer 
Sprungtabelle
JP (HL) (Z80)
IJMP    (AVR)
etc

In C geht das mal ausnahmsweise nicht so effizient.

von Novice (Gast)


Lesenswert?

Ev. mit Sprungtabelle
1
void mach0() {
2
    mach nr 0
3
}
4
5
void mach1() {
6
    mach nr 1
7
}
8
9
void mach2() {
10
    mach nr 2
11
}
12
13
void (*mach)(void)[4] = {mach0, mach1, mach2};
14
15
mach[a]();

von Rene H. (Gast)


Lesenswert?

H.Joachim S. schrieb:
> In C geht das mal ausnahmsweise nicht so effizient.

In C geht das genauso. Da tut sich Assembler und C nichts an.

von A. H. (ah8)


Lesenswert?

Mal eine Frage am Rande: Sind die Aktionen in den 16 Zweigen tatsächlich 
völlig unabhängig voneinander oder gibt es da Gemeinsamkeiten? Genauer 
gefragt, kann man die 16 Aktionen derart in Teilaktionen zerlegen, dass 
die Anzahl benötigter Teilaktionen merklich geringer wird? Könnten die 
Teilaktionen dann immer in der gleichen Reihenfolge ausgeführt werden?

von Jakob (Gast)


Lesenswert?

Und wohin "returnt" die void machx() Funktion?

Wahrscheinlich wieder in die Vergleichsroutine -
und dann kommt entweder ein JUMP (C: break) zum Ende der
Vergleichsroutine, oder sie wird genauso stur weiter
abgearbeitet...

Das hatten wir doch schon...

von Jakob (Gast)


Lesenswert?

A. H. (ah8)  schrieb:
> kann man die 16 Aktionen derart in Teilaktionen zerlegen, dass

Hatte ich auch schon mit:
"... der Wert von a in Gruppen zusammengefasst werden kann..."

Nützt aber nix, wenn der TO nicht mehr reinschaut.

Ich geh jetzt auch schlafen.

von Mein grosses V. (vorbild)


Lesenswert?

A. H. schrieb:
> Mal eine Frage am Rande: Sind die Aktionen in den 16 Zweigen tatsächlich
> völlig unabhängig voneinander oder gibt es da Gemeinsamkeiten?

Wenn es Gemeinsamkeiten gibt, findet der Compiler sie auch. Du wirst 
staunen, was da in dem resultierenden Asm bei rauskommen kann. Das merkt 
man immer sehr schön daran, wenn man in irgendeinem Case-Zweig einen 
Breakpoint setzen möchte, dieses aber nicht geht.

Das kriegt man händisch sehr selten besser hin. Und wenn man nicht weiß, 
wie der Compiler tickt, gar nicht.

Einfach den ganzen Krempel nacheinander hinschreiben, um den Rest 
kümmert sich der Compiler.

Natürlich nur bei eingeschalteter Optimierung.

von A. H. (ah8)


Lesenswert?

Jakob schrieb:
> Hatte ich auch schon mit:
> "... der Wert von a in Gruppen zusammengefasst werden kann..."

Nicht ganz. Ich möchte ja nicht a in Gruppen zusammenfassen, sondern die 
aus a resultierenden Aktionen in Gruppen zerlegen. Also etwa
1
if (a==1) {
2
  AktionA; AktionB;
3
}
4
else if (a==2) {
5
  AktionA; AktionC;
6
}
7
else if (a==3) {
8
  AktionB;
9
}
10
else if (a==4) {
11
  AktionB; AktionC;
12
...
13
}

von A. H. (ah8)


Lesenswert?

Mein grosses V. schrieb:
> Wenn es Gemeinsamkeiten gibt, findet der Compiler sie auch. ...

Ja, das will ich jetzt nicht abstreiten und ich bin auch absolut Deiner 
Meinung, dass es in den meisten Fällen wohl das Beste wäre, den Code 
einfach so stehen zu lassen (Optimierung hin oder her). Aber die Frage 
lautete nun mal, ob man das einfacher gestalten kann und ich glaube 
nicht, das der TO mit man den Compiler meinte. :-)

Wobei jede Antwort natürlich erst einmal darauf hinaus läuft, es 
anders zu machen. Ob anders wirklich auch einfacher heisst steht 
dann noch auf einem anderen Blatt.

: Bearbeitet durch User
von Bernard B. (bernard_b)


Lesenswert?

Easier : No.
Optimizing speed : Yes.

I program in Bascom, but I think the idea of what is done, and how to 
translate it back to your programming language is do-able. OK,. I assume 
16 steps. By the following you have a max. of 4 steps, rather than 
performing all 16 steps checking the value of "A":
1
If A < 9 then                 '1 .. 8
2
   If A < 5 then              '1 .. 4
3
      If A < 3 then           '1, 2 
4
         If A < 2 then
5
            Call Routine_1()
6
         Else
7
            Call Routine_2()
8
         End If
9
      Else                    '3, 4
10
         If A < 4 then
11
            Call Routine_3()
12
         Else
13
            Call Routine_4()
14
         End If
15
      End If
16
   Else ' > 4                 '5 .. 8
17
       If A < 7 then          '5, 6
18
          If A < 6 then
19
             Call Routine_5()
20
          Else
21
             Call Routine_6()
22
          End If
23
       Else                   '7, 8
24
          If A < 8 then
25
             Call Routine_7()
26
          Else
27
             Call Routine_8()
28
          End If
29
       End If
30
   End If
31
Else                          '9 .. 16
32
   If A <= 12 then
33
      If A < 11 then          '9, 10
34
         If A < 10 then
35
            Call Routine_9()
36
         Else
37
            Call Routine_10()
38
         End If
39
      Else                    '11, 12
40
         If A < 12 then
41
            Call Routine_11()
42
         Else
43
            Call Routine_12()
44
         End If
45
      End If
46
   Else                       '13 .. 16
47
       If A < 15 then
48
          If A < 14 then      '13, 14
49
             Call Routine_13()
50
          Else
51
            Call Routine_14()
52
          End If
53
       Else                   '15, 16
54
          If A < 16 then
55
             Call Routine_15()
56
          Else
57
             Call Routine_16()
58
          End If
59
       End If
60
   End If
61
End If
I admit, it is more code, but this will be executed faster in a 
microcontroller. If I'm not mistaken, a "Switch" or "Case" will be 
translated back into a multiple "If-Else"-construction. You can verify 
this by trying both and check the size of the final binairy file.

Next to it, it's good to know if your microcontroller is executing a 
"smaller then"-command faster than "larger then"-command. Sounds odd, 
but some microcontrollers operate by these unknown / hidden "features". 
Also in such case you can win some time.

Furthermore, I'm pretty sure, your routines for each value of "A" show 
some similarities. This can also be done by coding some general routines 
which has to be run through, depending for the value of "A".

P.S. I'm sorry for writing in English. I do speak better English than 
German.

--

Thou shalt use the [ pre ] [ /pre ] tags.

-rufus

: Bearbeitet durch User
von Harry L. (mysth)


Lesenswert?

Ugly! - perfect example for: "better do it the C-way!"

von Jasson J. (jasson)


Lesenswert?

As he wrote, it looks more complicated, but is faster to execute. The 
typical "divide and command" approach.
AND it´s harder to debug and become more and more complicate to 
maintain. If know 110% your argument won´t grow to more than 16 it´s 
okay, but when you need to change the tree it becomes painfull...


BUT, i´m not even sure, if this is faster than a switch case. Also the 
TO didn´t wrote about speed, just about readability. For example if the 
compiler mirros the switch-case to a if-else-default structure the 
divide-and-command approach is faster. But, if the compiler translates 
to relative or absolute jumps, the select-case would be faster. Maybe 
the translation depends on the optimization grade, if one calls a bunch 
of single instructions within the case, if one calls a single function, 
two+ following functions or if the called functions are defined as 
inline.

If the main goal is readability i would stick to select-case-default.
If you feel, you need some kind of a speed up, you can define the 
functions as inline (but results in bigger code size). "Maybe" this has 
a second effect which might bring an additional acceleration:
if the functions in the cases are inline, the compiler could treat the 
whole content of each case separately. So in his virtual mind he wraps a 
pair of brackets around the content and says, "Okay, this is a specific 
destination to jump to" and translates to a set of jump addresses. So 
the compiler might translate the select-case differently because of the 
"inline". If the functions are NOT inline, and the compiler optimizes to 
code size, It might end in the if-else translation behind the scenes.


May be another approach could be to use an array of function pointers.
The idea is, one defines a set of functions, which would reflect the 
areas of the select-case bodies or those of the if-else brackets.
So the argument one would use in the select-case would be used in the 
array to reference the destination function to jump to.
Within that destination function one would write the actions to execute 
or other function calls.

cons:
- reference argument must be checked before applied to the array or it 
must be safe that argument dosn´t exceed the array
- argument must be a continuous value (?)
- names of functions get hidden behind pointers

pros:
- one can define a destination function more than once in the 
func-point-array(applies to "if((arg == 2) || (arg == 6)) doJob2();"
- "i think" the execution time becomes deterministic, since one doesn´t 
compare the argument to other values instead of using it directly to 
jump somewhere. So one isn´t depended to the role-out-scheeme of the 
compiler.
- "i don´t think" there is much for the compiler to interpret, or let´s 
say it will be a smaller difference compared to the decision if to 
translate a select-case to a bunch of if´s or jumps. So one knows more 
exactly what´s going on in the controller. I have seen this approach in 
RTOS´s as callback solution.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

For the BASCOM example above, where sixteen different functions are 
being called for sixteen distinct values, a table-driven approach using 
function pointers would surely be faster -- except that a decent 
compiler's optimization could be expected to break it down to exactly 
that.

--

Für das BASCOM-Beispiel oben, das anhand 16 verschiedener Werte 16 
verschiedene Funktionen aufruft, dürfte die Verwendung einer Tabelle mit 
Funktionspointern effizienter sein. Wobei man sowieso davon ausgehen 
sollte, daß die Optimierung eines anständigen Compilers mehr oder 
weniger das gleiche daraus bastelt.

von Der E. (rogie)


Lesenswert?

H.Joachim S. schrieb:
> Bei grösseren Konstrukten kann die Laufzeit schon ein Problem werden. Es
> wird eben von oben nach unten stur verglichen. Wenn das stört, kann man
> eine Vorauswahl treffen und die Sache auf mehrere switch/case-Blöcke
> aufteilen.
>
> In Assembler hätte man da deutlich schnellere Alternativen mit einer
> Sprungtabelle
> JP (HL) (Z80)
> IJMP    (AVR)
> etc
>
> In C geht das mal ausnahmsweise nicht so effizient.

"Gute" C(++) Compiler machen genau dies auch bei switch case 
Anweisungen, sofern das möglich ist. Das konnte schon Borland C++ 3.1.

Hier mal ein Beispiel, was der C++ Compiler von Rad Studio Seattle 
daraus macht:
1
int j,i;
2
3
switch(j)
4
{
5
  case 0 : i=0; break;
6
  case 1 : i=1; break;
7
  case 2 : i=2; break;
8
  case 3 : i=3; break;
9
  case 4 : i=4; break;
10
}

Entsprechender assemblierter Ausschnitt:
1
Unit12.cpp.18: switch(j)
2
0040303C 8B55CC           mov edx,[ebp-$34]
3
0040303F 83FA04           cmp edx,$04
4
00403042 7744             jnbe $00403088
5
00403044 FF24954B304000   jmp dword ptr [edx*4+$40304b]
6
0040304B 5F               pop edi
7
0040304C 304000           xor [eax+$00],al
8
0040304F 66304000         xor [eax+$00],al
9
00403053 6F               outsd 
10
00403054 304000           xor [eax+$00],al
11
00403057 7830             js $00403089
12
00403059 40               inc eax
13
0040305A 008130400033     add [ecx+$33004030],al
14
00403060 C9               leave 
15
00403061 894DC8           mov [ebp-$38],ecx
16
00403064 EB22             jmp $00403088
17
Unit12.cpp.21: case 1 : i=1; break;
18
00403066 C745C801000000   mov [ebp-$38],$00000001
19
0040306D EB19             jmp $00403088
20
Unit12.cpp.22: case 2 : i=2; break;
21
0040306F C745C802000000   mov [ebp-$38],$00000002
22
00403076 EB10             jmp $00403088
23
Unit12.cpp.23: case 3 : i=3; break;
24
00403078 C745C803000000   mov [ebp-$38],$00000003
25
0040307F EB07             jmp $00403088
26
Unit12.cpp.24: case 4 : i=4; break;
27
00403081 C745C804000000   mov [ebp-$38],$00000004
28
Unit12.cpp.28: }
29
00403088 8B45D8           mov eax,[ebp-$28]

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

Jasson J. schrieb:
> As he wrote, it looks more complicated, but is faster to execute. The
> typical "divide and command" approach.

There are several ways to implement switch statements. If a compiler 
isn't too stupid, one of the variants is a binary search tree like the 
one you described.

A decent compiler analyzes attributes of the cases, like count, 
distance, distribution pattern etc, and together with the speed/space 
tradeoff hint it choses the algorithm to use. If it is really decent, it 
may even split the cases into a few groups handled in a different way.

Different algorithms have different advantages and drawbacks. Often 
people assume that 10 consecutive numbers should always result in a 
table driven approach. However if you look at the code produced, you may 
see a significant degree of fixed overhead associated with it. Bounds 
checking and table access may result in a number of clock cycles which, 
on average, is higher as a sequence of decrement/branch-if-zero or 
compare-and-branch instructions, if the number of cases is low, e.g. 
just 10.

This decision may result in different choices on different machines, 
because they are not all the same in this respect. Especially if you 
take branch prediction effects into account. And together with 
statistics collected in test runs with a specially prepared executable, 
the compiler may perhaps find out that there is a dedicated preference 
for a few cases in actual use.

> AND it´s harder to debug and become more and more complicate to
> maintain. If know 110% your argument won´t grow to more than 16 it´s
> okay, but when you need to change the tree it becomes painfull...

That's one of the reasons why it is preferrable to let the compiler do 
the optimization instead of doing manually. Adding a few cases may 
completly throw over your manual decision tree, while a compiler isn't 
hindered by yesterdays choices.

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

H.Joachim S. schrieb:
> In Assembler hätte man da deutlich schnellere Alternativen mit einer
> Sprungtabelle

Erstens kann der Compiler das auch. Wenn er kann. Und du vergisst dabei, 
dass ein Tabellenverfahren erst einmal auf obere & untere Grenze prüfen, 
die Tabellenadresse berechnen und deren Inhalt laden muss.

Tatsächlich kann die Laufzeiteffizienz von (z.B. 8051)
  compare and branch
  compare and branch
  compare and branch
  compare and branch
  ...
oder (viele)
  subtract lower bound
  branch if lower
  branch if equal
  subtract difference to next ordered case
  branch if equal
  subtract difference to next ordered case
  branch if equal
  subtract difference to next ordered case
  branch if equal
  ...
bei nicht zu vielen Fällen ziemlich gut sein. Nachrechnen kann da 
nützlicher sein als anfängliches Bauchgefühl.

> In C geht das mal ausnahmsweise nicht so effizient.

Prinzipiell nicht, oder bloss weil der Autor des Compiler nicht auf 
diese Idee kam?

: Bearbeitet durch User
von Dieter F. (Gast)


Lesenswert?

Ich verstehe die Diskussion nicht so recht.

Der TO fragt konkret nach "== x"-Abfragen - für mich schlicht 
"switch-case" und nichts anderes. Keine > oder < Abfragen, nur "==".

Mir ist kein besseres Konstrukt in C bekannt.

von Peter D. (peda)


Lesenswert?

Rene H. schrieb:
> Der Kompiler macht aber aus switch/case/break das selbe wie if/else.

Nö, in der Regel optimiert er switch/case deutlich besser (binärer 
Suchbaum). Und er erkennt automatisch, ab wann eine Sprungtabelle bei 
aufeinanderfolgenden Case effektiver ist. Auch optimiert er gleiche 
Sequenzen vor den breaks weg.

Oftmals ergibt sich aber eine riesige Optimierung, wenn der 
Programmierer nur etwas Gehirnschmalz investiert und prüft, worin sich 
die Cases ähnlich sind. Dann kann man die Cases oft völlig wegoptimieren 
zu einer Funktion mit Argumenten. Und wenn sich die Argumente nicht aus 
dem Case berechnen lassen, nimmt man sie eben aus einem Array.

Ein Beispiel findet man in meinem DCF77 Code.

von Vancouver (Gast)


Lesenswert?

Falls die "mach nr #" komplizierter sind, würde sich ein Array mit 
Funktionspointern anbieten, dann sparst Du Dir das ganze 
if/else-Konstrukt für den Preis eines Funktionsaufrufs. Wie das geht, 
wurde z.B. hier diskutiert: 
http://stackoverflow.com/questions/252748/how-can-i-use-an-array-of-function-pointers

Fall "mach nr #" nur ein einfacher Ein- oder Zweizeiler ist, dann ist 
switch/case/break sicher das Mittel der Wahl. Bei den meisten Compilern 
erzeugt das den gleichen oder ähnlichen Maschinencode wie eine 
if/else-Kaskade, ist aber schöner zu lesen, je nach Geschmack.

von A. H. (ah8)


Lesenswert?

Also wenn ich die Frage des TO: „Wie kann ich das ganze einfacher 
gestalten?“ richtig interpretiere, dann geht es darum, wie man das 
Problem einfacher notieren kann. Es geht also um die Gestaltung des 
Quelltextes und das wäre zunächst einmal völlig unabhängig von der 
Frage, welchen Zielcode der Compiler daraus gegebenenfalls konstruiert 
oder auch nicht und wie optimal dieser sein mag.

Leider ist nicht ganz klar, was einfacher in diesem Zusammenhang 
konkret heißt, so dass ich einfacher zunächst einfach mal als kürzer 
interpretieren würde.

Dazu muss man nun feststellen, dass es für den allgemeinen Fall kaum 
eine kürzere Form geben wird als eine Serie von if bzw. else if 
Klauseln. Selbst ein switch wäre nicht wesentlich kürzer. Zwar spart 
man den Vergleichsoperator und die Klammern um die Bedingung und 
eventuell das compound statement – letzteres auch nur unter der 
Voraussetzung, dass es keine Variablen gibt, die bezüglich eines Zweiges 
lokal sein sollen – dafür aber wird ein zusätzliches break nötig, 
dessen Existenz obendrein vom Compiler nicht geprüft werden kann. 
Letzteres gilt zwar auch für jedes else aber bei Test auf Gleichheit 
bei disjunkten Vergleichswerten wird ein fehlendes else im Gegensatz 
zu einem fehlenden break keine Änderung der Semantik bewirken und 
Unterschiede in der Performance dürften durch den Compiler wegoptimiert 
werden. Darüber hinaus gestatten if Anweisungen komplexere 
Vergleichsbedingungen, so das eine Serie von if Klauseln bei 
vergleichbarer Länge die robustere und letztlich auch flexiblere Lösung 
wäre.

Auch eine Sprungtabelle macht den Code im allgemeinen Fall keinesfalls 
kürzer, sondern ganz Gegenteil, länger, denn nun braucht man pro Zweig 
zwei Code-Einheiten: einmal den Eintrag in die Sprungtabelle und einmal 
den Funktionskopf der Funktion, in der die Aktionen eines Zweiges 
zusammengefasst werden sollen. Das wäre nur dann kürzer, wenn die 
Funktionen für die einzelnen Zweige, aus welchen Gründen auch immer, 
bereits existieren, was aber schon wieder einen Spezialfall darstellt.

Eine Verkürzung des Codes ließe sich meines Erachtens nach nur dann 
erreichen, wenn durch Umkodierung der Vergleichswerte die Zahl der 
Zweige substantiell verkleinert werden kann. Zum Beispiel ließe sich der 
Code:

1
if (a==1) {
2
  AktionA; AktionB;
3
}
4
else if (a==2) {
5
  AktionA; AktionC;
6
}
7
else if (a==3) {
8
  AktionB;
9
}
10
else if (a==4) {
11
  AktionB; AktionC;
12
...
13
}

folgendermaßen umkodieren:

1
enum Actions { AktionA = (1<<0), AktionB = (1<<1), AktionC = (1<<2), AktionD = (1<<3) };
2
3
int actions[] = {
4
  /* a==1 */  AktionA || AktionB, 
5
  /* a==2 */  AktionA || AktionC,
6
  /* a==3 */  AktionB,
7
  /* a==4 */  AktionB || AktionC
8
  
9
};
10
11
if ( actions[a] && AktionA ) {
12
  // do action A
13
}
14
if ( actions[a] && AktionB ) {
15
  // do action B
16
}
17
if ( actions[a] && AktionC ) {
18
  // do action C
19
}
20
if ( actions[a] && AktionD ) {
21
  // do action D
22
}

Das bringt, wie gesagt, natürlich nur etwas, wenn die Anzahl der Zweige 
dadurch substantiell kleiner wird und hängt obendrein von einigen 
Randbedingungen ab. Selbst dann würde ich die Anwendung einer solchen 
Technik noch von der gleichzeitigen Verbesserung der Lesbarkeit abhängig 
machen und die wäre nur gegeben, wenn das so umstrukturierte Programm 
die Struktur des zu Grunde liegenden Problems besser widerspiegelt.

Ob es das tut lässt sich ohne Kenntnis des Problems und der Aktionen in 
den Zweigen aber nicht entscheiden.

: Bearbeitet durch User
von Bernard B. (bernard_b)


Lesenswert?

Rufus: thank you for modifying my message.

The main problem we face together here, is additional but missing 
information. There's no info about the used microcontroller, if each 
value (A) requires a different follow-up routine, and what 
microcontroller is used (i.e. available flash-memory).

So far we guess what the best solution might be, and we won't be able to 
find a consensus. Like said, we're all providing information (with our 
best intentions and available knowledge), but it's the Topic starter who 
should supply more detailed information about his specific project. I 
think it would be a better (kick)start.

: Bearbeitet durch User
von Achim S. (achims)


Lesenswert?

Hallo
du sprichst von Randbedingungen. In den unterschiedlichen ifs kann z.B. 
so was drin stehen. Es hängt immer von der Abfrage nach a ab:
1
if (a==1)              // wenn Key 0 gedrückt
2
      {
3
      b=0;
4
      var &=~(0b00010000);        // LED ein
5
      }
oder sowas
1
if (a==32)              // wenn Key 5 gedrückt
2
      {                  // Einknopfbedienung
3
      b=5;              // Anzeige Display
4
      if (touch_ready)          // wenn touch ready auf 1 ...
5
        {
6
        if (h==0)          // wenn Hilfvariable auf 0 ...
7
          {
8
          h=1;          // Hilfsvariable auf 1
9
          var &=~(0b10000000);    // LED 1+4 ein
10
          }
11
        else
12
          {
13
          h=0;
14
          var|=(0b10000000);    // LED 4 aus
15
          }
16
        touch_ready = 0;        // setze touch rady auf 0
17
        }
18
      }
19
    else
20
      {
21
      touch_ready = 1;          // touch ready auf 1 setzen
22
      }
Wahrscheinlich ist die Version mit if() die passende Version. Je nach 
Grund und Rahmenbedingung gibt es verschiedene Lösungen dazu. Ob eine 
andere Lösung immer ein Vorteil bringt, ist wahrscheinlich zu 
bezweifeln.
Danke für eure Hilfe
achim

von Bernard B. (bernard_b)


Lesenswert?

Concerning my first message about the "divide and command"-approach 
versus the "case/switch"-solution, I'd like to clarify some things.

Assume, you got a process, running the value A from 1 to 16.

By using "divide and command"-approach, each value will be found in 4 
steps.
This means, (16 x 4=) 64 steps total.

Now, if you go for "case/switch" or the preliminary suggested 
"if"-solution, you get the following :
If A = 1, result will be found in 1 step.
If A = 2, result will be found in 2 steps.
If A = ....
If A = ....
If A = 15, result will be found in 15 steps.
If A = 16, result will be found in 16 steps.
In other words, the total steps will be 1 + 2 + .. 15 + 16 = 136 steps.

However, I think it's wise to re-consider the routines, which should be 
run after the found value of "A" has been found. Best practice is to 
write the whole code in "if-else"-construction. After done, it's time to 
analyze the written code.
1) At this point it's time to figure out if you can detect some 
patterns, which can be translated into condensed math-formulae.
2) Next step is to see if the available program you write the code in, 
offers additional features to condense this code.
3) If both solutions do not help, it's time to re-consider the 
electronic schematic of your device.

But, it's more wise to start with step 3), when designing an schematic, 
including the location of pins at your microcontroller (and ofcourse, 
available memory).

: Bearbeitet durch User
von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Achim S. schrieb:
> a wird zwischen 0 und 64 liegen.
 Wahrscheinlich meinst du zwischen 0 und 63 ?

> Bei 10 Abfragen wird das ganz schön lang. Wahrscheinlich muss ich das
> auf 16 Abfragen verlängern. Wird noch länger.
> Wie kann ich das ganze einfacher gestalten?
 Wie du auf 10 oder 16 Abfragen kommst, ist mir unklar - werden da nur
 bestimmte Werte bearbeitet, andere sind 'else', oder meinst du dass
 mindestens 10 oder 16 Abfragen nötig sind, um den Wert zu bestimmen
 und entsprechende Aktion durchzuführen ?

 Mach doch mal ein Array mit Sprungadressen, etwa so:
 {KeyPress_00,    // * Key0 -> LEDn ein
  KeyPress_01,    // * Key1
  foo, foo, foo   // * 2-4 no action
  ...
  ...
  KeyPress_32,    // * Key5 -> LEDchk -> Check4Touch
  ...
  ...
  KeyPress_63     // * mach irgendetwas
  }
  Mit zusätzlichen Bemerkungen in der Tabelle wird es übersichtlicher
 und einfacher zu bearbeiten - schneller ist es auf jeden Fall.

von (prx) A. K. (prx)


Lesenswert?

Bernard B. schrieb:
> Concerning my first message about the "divide and command"-approach

The algorithm is officially called "binary search" and is fairly well 
known in computer science. And it was already used in implementations of 
switch statements at least 30 years ago.

> Now, if you go for "case/switch" or the preliminary suggested

Since efficiency is your concern, you have to look at the code produced. 
You cannot simply guess the complexity of the resulting code by looking 
at the source code.

> In other words, the total steps will be 1 + 2 + .. 15 + 16 = 136 steps

If you place your 1..16 iteration within the function itself, or at 
least close enough, you could as well end in machine code not doing any 
of those decisions, e.g. requiring 0 steps. If the optimizer recognizes 
the correlation between iteration and decisions. ;-)

: Bearbeitet durch User
von Sheeva P. (sheevaplug)


Lesenswert?

Achim S. schrieb:
> Hallo
> in einem Programm muss ich a ca. 10 mal abfragen auf wahr oder falsch.
> Habe es bisher so gemacht:
>
1
> if (a==1)
2
>   { dann mach nr 1;}
3
> 
4
> if (a==2)
5
>   { dann mach nr 2;}
6
> 
7
> if (a==3)
8
>   { dann mach nr 3;}
9
> 
10
> if (a==4)
11
>   { dann mach nr 4;}
12
> ....
13
>
> Eine andere Möglichkeit ist:
>
1
> if (a==1)
2
>   { dann mach nr 1;}
3
>     else if (a==2)
4
>       { dann mach nr 2;}
5
>     else if (a==3)
6
>       { dann mach nr 3;}
7
>     else if (a==4)
8
>       { dann mach nr 4;}
9
> ....
10
>

Das ist logisch nicht dasselbe. Wenn "mach..." die Variable a ändern 
kann, prüft der erste Code die gegen den geänderten Wert, der zweite 
nicht.

> Bei 10 Abfragen wird das ganz schön lang. Wahrscheinlich muss ich das
> auf 16 Abfragen verlängern. Wird noch länger.
> Wie kann ich das ganze einfacher gestalten?

Die Möglichkeit mit switch()..case wurde schon genannt. Wenn "mach nr. 
1", "mach nr. 2" ... "mach nr. n" dieselbe Funktionssignatur haben, 
kannst man aber auch shiqq mit Funktionszeigern arbeiten:
1
#include <stdio.h>
2
3
typedef int (*Funptr_t)(int);
4
5
typedef struct {int z; Funptr_t p;} Int2Code_t;
6
7
int mach_nr_1(int b) { return b + 2; }
8
int mach_nr_2(int b) { return b - 2; }
9
int mach_nr_3(int b) { return b * 2; }
10
int mach_nr_4(int b) { return b / 2; }
11
12
13
int main(void) {
14
15
    Int2Code_t i2c[] = {
16
        {1, mach_nr_1},
17
        {2, mach_nr_2},
18
        {3, mach_nr_3},
19
        {4, mach_nr_4}
20
    };
21
22
    int a = 0;
23
    printf("Eingabe: ");
24
    a = getchar() - '0';
25
26
    for(int i = 0; i < (int)(sizeof(i2c) / sizeof(Int2Code_t)); ++i) {
27
        if( i2c[i].z == a ) {
28
            printf("Ergebnis: %d\n", i2c[i].p(a));
29
  }
30
    }
31
    
32
    return 0;
33
}

Wenn Deine Variable "a" mehr oder weniger alle Werte zwischen 0 und n 
annehmen kann, kannst Du Dir den Typen "Int2Code_t" aber auch sparen und 
mit Array-Indizes arbeiten und / oder bei Lücken (also Werten, die Deine 
Variable a nicht annehmen kann) eine Dummy-Funktion einsetzen:
1
#include <stdio.h>
2
3
typedef int (*Funptr_t)(int);
4
5
int mach_nr_0(int b) { return b - b - 127; } /* dummy */
6
int mach_nr_1(int b) { return b + 2; }
7
int mach_nr_2(int b) { return b - 2; }
8
int mach_nr_3(int b) { return b * 2; }
9
int mach_nr_4(int b) { return b / 2; }
10
11
12
int main(void) {
13
14
    Funptr_t i2c[] = {
15
        mach_nr_0,
16
        mach_nr_1,
17
        mach_nr_2,
18
        mach_nr_3,
19
        mach_nr_4
20
    };
21
22
    int a = 0;
23
    printf("Eingabe: ");
24
    a = getchar() - '0';
25
26
    if(a >= 0 && a < (int)(sizeof(i2c) / sizeof(Funptr_t))) {
27
        printf("Ergebnis: %d\n", i2c[a](a));
28
    }
29
    
30
    return 0;
31
}

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.