C ohne if() - in einzelnen Fällen möglich?

#7093359
Lesenswert?

Der Zustand einiger voneinander unabhängiger Bits eines Bytes (uint8_t) 
wird überwacht und auswertet.
Ein vereinfachtes und extrem verständliches Beispiel dazu wäre:
Bit 3 == 1  ---> LED on
Bit 3 == 0  ---> LED off
1
uint8_t b1;
2
. . . . .
3
. . . . .
4
  if((b1 & 0x08) == 0x08)   // if b1 == xxxx1xxx
5
  {
6
    PORTA.OUT |= 0x08;      // set PA3 (LED on)
7
  }
8
  else                      // if b1 == xxxx0xxx
9
  {
10
    PORTA.OUT &= 0xF7;      // clear PA3 (LED off)
11
  }
12
. . . . .
Kann man dieses Beispiel irgendwie ohne if() umsetzen?
(ist nicht sehr wichtig, nur eine theoretische Frage)
Gast #7093569
Lesenswert?

Mich würde interessieren, was der TO gegen if / else hat.
Klar, damit kann man unübersichtlich verschachteln, aber bei dem 
genannten Beispiel ist das ja nicht so.
Und andere Konstrukte, wie ?: wird der Compiler zum ebenso effizienten 
Code auflösen, ich vermute, dass auch die Variante von Gerald K. 
annähernd den gleichen Assemblercode ergibt.
Also, warum nicht if und else?
Gast #7093668
Lesenswert?

Aus meiner Sicht, gibts keinen Weg an if Konstrukten vorbei.
Klar kann man in manchen Fällen einen alternativen Weg finden.
Nur ist es dann lesbarer?
Daran entscheidet sich der Sinn in erster Linie.

Natürlich macht es Sinn möglichst flache Programme zu schreiben.
Möglichst geringe Schachtelungstiefen, einfache Bedingungen.
Eben genau, wegen der Lesbarkeit.

Ein Weg:
Viele kleine gut benannte Funktionen, statt wenige aufgeblähte.
Gast #7093688
Lesenswert?

Freizeit Coder schrieb:
> Wie wäre es mit einer Switch Case Anweisung?
> Schaue hier :
> https://www.c-howto.de/tutorial/verzweigungen/switch-case/
>
> int a=2;
>
> switch(a) {
>   case 1: printf("a ist eins\n"); break;
>   case 2: printf("a ist zwei\n"); break;
>   case 3: printf("a ist drei\n"); break;
>   default: printf("a ist irgendwas\n"); break;
> }

Hmm,
und was hat jetzt diese deine Erläuterung
zu tun mit
der Fragestellung (= erster Beitrag).
Gast #7093757
Lesenswert?

Also: ein ?:, Ggf mit Komma statt {} kann viele ifs ersetzen.

Eine binäre Zuweisung aus einer binären Frage geht meist ohne jedes If 
oder dessen äquivalent


 && Und || erlauben viele if-äquivalente Konstrukt 
(Kurzschluss-Auswertung)

Switch/Case, for und while können immer als If missbraucht werden, als 
Else nur teilweise.

Weitere Möglichkeiten über funktionsptr-Arrays und und und.

Wenn Du einen der genannten Ansätze nicht Kennst, einfach fragen
Gast #7093830
Lesenswert?

Wenn AVR:
> von Docureader (Gast)  10.06.2022 16:28

PORTA.OUT = __builtin_avr_insert_bits(0xfffff2ff, b1, PORTA.OUT);
Wird in 4 Maschinen-Befehlen (in, lds, bst, out) ausgeführt.
Kürzer, schneller, eleganter und edler geht es nicht.
Man muss nur wissen wie.
Gast #7093893
Lesenswert?

Eggsbert schrieb:
> Kürzer, schneller, eleganter und edler geht es nicht.

Von anderen AVR Modellen bin ich gewohnt, dass das Setzen/Löschen eines 
einzelnen Bits in jeweils eine einzige Instruktion übersetzt wird.

Auszug aus einem Assembler Listing:
1
    PORTB |= (1<<PB4);
2
  e4:  c4 9a         sbi  0x18, 4  ; 24
3

4
    PORTB &= ~(1<<PB5);
5
  e6:  c5 98         cbi  0x18, 5  ; 24

Das hat allerdings nichts mit der Frage des TO zu tun.
Gast #7093895
Lesenswert?

Ich schrieb:
>> ((b1 & 0x08) == 0x08) && PORTA.OUT |= 0x08 || PORTA.OUT &= 0xF7;

Apollo M. schrieb:
> Sieht korrekt aus, ob short-logic hier bekannt/beliebt ist ...?

Bei mir ist es jedenfalls nicht beliebt. Aber wenn er schon so fragt ...

Ich kenne dieses Konstrukt eher von Shell Scripten, zum Beispiel:
1
wget http://whatever/data.zip || exit 1
2
unzip data.zip || exit 1
3
mv /target/folder/summary.csv /target/folder/summary.old || exit 1
4
copy data/summary.csv /target/folder || exit 1
5
echo "Download of new summary succeeded"

In diesem Fall bricht das Script bei jedem Fehler sauber ab, ohne 
Folgefehler zu produzieren. Insbesondere das Umbenennen von summary.csv 
nach summary.old soll nur stattfinden, wenn der Download und das 
Auspacken der ZIP Datei erfolgreich war.
Gast #7093902
Lesenswert?

> Bei mir ist es jedenfalls nicht beliebt.
Short-Circuit evaluation ist elementarer Bestandteil von C und davon 
abgeleiteten Sprachen.

Um die zu umgehen, muss man reichlich sinnlosen und aufgeblähten Code 
schreiben.
Gast #7093927
Lesenswert?

A. S. schrieb:
> Weitere Möglichkeiten über funktionsptr-Arrays und und und.

Oder ganz allgemein: reine Daten-Zweige sind auf zig weitere Arten 
möglich (Rückgabewert abhängig vom Argument). Die Wege für den 
kontrollfluas sind begrenzter und größtenteils schon gelistet (mir fällt 
so schnell kein weiterer ein, vielleicht über mehr experimentelle Sachen 
wie Exit, ljump, typeof in Makros, ...
#7093928
Lesenswert?

Gerald K. schrieb:
> HildeK schrieb:
>> Also, warum nicht if und else?
>
> War nur eine theoretische Frage des TO's ohne praktischem Nutzen.

Aber der Hintergrund der Frage wäre schon interessant zu wissen, auch im 
Bezug auf die Antworten. Geht es eher darum, zu vermeiden, dass der 
Computer eine Verzweigung ausführt? Dann wird die Antwort sehr 
kompliziert (oder sehr einfach, je nach Sichtweise). Oder ist es eher 
eine sprachliche Frage, also welche Sprachkonstrukte es gibt, die im 
Prinzip das gleich leisten können?
Oder gibt es keinen Hintergrund, und die Frage ist wirklich komplett aus 
der Luft gegriffen? Dann ist sie irgendwie sinnlos.
Gast #7093945
Lesenswert?

>   if((b1 & 0x08) == 0x08)   // if b1 == xxxx1xxx
kann man kürzer schreiben:
    if(b1 & 8)
Ein Ausdruck ist dann wahr, wenn er ungleich 0 ist, man muss also nicht 
explizit mit 8 vergleichen, es reicht, wenn !=0.

Korrektur:
> Wird in 4 Maschinen-Befehlen (in, lds, bst, out) ausgeführt.
Es sind 5 Befehle: in, lds, bst, bld, out

> Von anderen AVR Modellen bin ich gewohnt, dass das Setzen/Löschen
> eines einzelnen Bits in jeweils eine einzige Instruktion übersetzt wird.
Schon, aber bei bst (Bit Store from Register to T) wird das Bit im 
Status-Flag T zwischengespeichert und mit bld (Bit Load from T to 
Register) ins Zielregister kopiert (eigentlich würde man es von store 
und load genau andersherum erwarten).
#7093947
Lesenswert?

Rolf M. schrieb:
> Aber der Hintergrund der Frage wäre schon interessant zu wissen, auch im
> Bezug auf die Antworten.

Georg M. schrieb:
> Der Zustand einiger voneinander unabhängiger Bits eines Bytes (uint8_t)
> wird überwacht und auswertet.

Da habe ich jetzt rausgelesen, normalerweise sind es mehr Bits als in 
dem Beispiel. Deshalb:

Ralf G. schrieb:
> 'switch/case'
> Sieht manchmal schöner aus...

Jedes mögliche Bitmuster bekommt seinen 'case-Zweig'. Das sieht für mich 
dann aufgeräumter aus als immer 'if-else-if-if-else'...
(Firma: @home) #7094023
Lesenswert?

Eggsbert schrieb:
> PORTA.OUT = __builtin_avr_insert_bits(0xfffff2ff, b1, PORTA.OUT);
> Wird in 4 Maschinen-Befehlen (in, lds, bst, out) ausgeführt.
> Kürzer, schneller, eleganter und edler geht es nicht.
> Man muss nur wissen wie.

Verstehe nur Bahnhof und blicke es nicht.
Kann das mal erklärt werden hins. 0xfffff2ff, ...
Wo kommt der Wert her? Warum ist der so lang?

Der Zweck wird von gnu genannt, aber wie der damit erfüllt wird kapiert 
ich auch nicht. Wie kann ich da über mehrere Ports an den Bits einfach 
rumfummeln?
Gast #7094033
Lesenswert?

Apollo M. schrieb:
> Eggsbert schrieb:
>> PORTA.OUT = __builtin_avr_insert_bits(0xfffff2ff, b1, PORTA.OUT);
>> Wird in 4 Maschinen-Befehlen (in, lds, bst, out) ausgeführt.
>> Kürzer, schneller, eleganter und edler geht es nicht.
>> Man muss nur wissen wie.
>
> Verstehe nur Bahnhof und blicke es nicht.
> Kann das mal erklärt werden hins. 0xfffff2ff, ...
> Wo kommt der Wert her? Warum ist der so lang?
>
> Der Zweck wird von gnu genannt, aber wie der damit erfüllt wird kapiert
> ich auch nicht. Wie kann ich da über mehrere Ports an den Bits einfach
> rumfummeln?


Mal abgesehen davon, daß das mit "..2FFF" falsch ist (und vom Beitrag 
Schreiber inzwischen  korrigiert),
braucht das NIEMAND verstehen, der nicht optimierten Assemblercode für 
genau diese Prozessor-Architektur erstellen wollen.
Alle anderen schreiben vernünftig lesbaren und kommentierten C-Code, den 
sie selbst verstehen und auch in 1/2 Jahr noch kampieren (dank der 
eigenen Kommentare).
Den Rest besorgt der C-Compiler mit Optimierungslevel 3 von 4 oder der 
höchsten.

Gruss
Gast #7094117
Lesenswert?

EAF schrieb:
> Aus meiner Sicht, gibts keinen Weg an if Konstrukten vorbei

Schade eigentlich.

Damit bist du ungeeignet um hochoptimierte Assemblerprogramme zu 
erstellen.

Denn ein Sprung unterbricht auf vielen Prozessorarchitekturen die 
Pipeline und kostet mehr Takte als z.B. das hier angemessene

Mario M. schrieb:
> tmp = PORTA.OUT & 0xf7;
> PORTA.OUT = tmp | b1 & 8;

Komplette Programme ohne einen einzigen Sprung sind natürlich arg 
limitiert: sie laufen nur ein Mal ab und können nicht viele Dinge 
treiben. Aber es gibt sie, so auf dem Level der AWL einer SPS.
Gast #7094141
Lesenswert?

HildeK schrieb:
> Mich würde interessieren, was der TO gegen if / else hat.

Abgesehen von "Code beliebig komplex schreiben" fällt mir auf die 
schnelle tatsächlich eine Prozessorplattform ein, die einerseits eine 
extrem schlechte Branch-Performance aufweist, gleichzeitig vom 
Hersteller aber jahrelang einen Compiler spendiert bekam der nicht fähig 
war, beispielsweise ein "if/else" zu einer (wesentlich performanteren) 
bedingten Zuweisung zu optimieren, wenn mans nicht extra als 
Ternäroperation hinschrieb.
Gast #7094212
Lesenswert?

vn nn schrieb:
> der nicht fähig
> war, beispielsweise ein "if/else" zu einer (wesentlich performanteren)
> bedingten Zuweisung zu optimieren, wenn mans nicht extra als
> Ternäroperation hinschrieb.

Naja...
Ist es doch so, dass das if() Statement für den Kontrollfluss zuständig 
ist, und der ?: Operator für Daten.
Optimierungen werden nicht vom Standard eingefordert.
Also eigentlich alles richtig.

Deiner Aussage nach sind die betreffenden Compiler "besser" geworden.
Also auch da alles richtig und gut so.
Gast #7094302
Lesenswert?

EAF schrieb:
> Ist es doch so, dass das if() Statement für den Kontrollfluss zuständig
> ist, und der ?: Operator für Daten.

Was natürlich in sehr vielen Alltagssituationen aufs gleiche hinaus 
läuft, insbesondere bei komplexer Regelungstechnik statt Pins toggeln.

EAF schrieb:
> Optimierungen werden nicht vom Standard eingefordert.

Hat auch keiner behauptet, man geht im 21. Jahrhundert halt davon aus 
dass ein Compiler zumindest ansatzweise sinnvoll optimieren kann.

EAF schrieb:
> Also eigentlich alles richtig.

Natürlich. Es fordert ja auch kein Standard dass ich mir nicht von 
hinten durchs Knie ins Auge schießen kann, somit alles richtig.

EAF schrieb:
> Deiner Aussage nach sind die betreffenden Compiler "besser" geworden.

Ich wüsste nicht wo ich das geschrieben haben soll, ich kenn lediglich 
die aktuelle Situation nicht, nachdem ich schon vor einigen Jahren den 
Arbeitsgeber gewechselt habe.
Nachdem der Prozessorhersteller aber bis heute immer noch auf seine 
mistigen proprietären Compiler statt auf GCC oder LLVM setzt und immer 
noch damit kämpft, überhaupt mal den Standard einzuhalten, gehe ich 
nicht davon aus dass er übers simpelste hinausgehende Optimierungen 
hinbekommt.

EAF schrieb:
> Also auch da alles richtig und gut so.

Ja, alles gut, es gibt nichts zu sehen, Mindestmaß ist ja erfüllt, mehr 
Ansprüche hat ja eh keiner.
Gast #7094353
Lesenswert?

vn nn schrieb:

> Abgesehen von "Code beliebig komplex schreiben" fällt mir auf die
> schnelle tatsächlich eine Prozessorplattform ein, die einerseits eine
> extrem schlechte Branch-Performance aufweist, gleichzeitig vom
> Hersteller aber jahrelang einen Compiler spendiert bekam der nicht fähig
> war, beispielsweise ein "if/else" zu einer (wesentlich performanteren)
> bedingten Zuweisung zu optimieren, wenn mans nicht extra als
> Ternäroperation hinschrieb.

Sowas kann im konkreten Fall sicher nicht das Problem sein. Denn: es 
handelt sich ganz offensichtlich ja nicht um eine simple Zuweisung mit 
Bedingung. Allerhöchstens dann, wenn die Zielarchitektur 
Bitinstruktionen kennt.

Damit dein Geschwalle also überhaupt irgendeinen praktischen Nutzen 
entfalten kann, braucht es eine Architektur sowohl mit Codecache als 
auch mit der der Fähigkeit zu Bitinstruktionen auf jedes beliebige Ziel.

Da wird die Luft doch schon arg dünne...

Und wenn es so eine Architektur gibt, der Compiler das aber nur für die 
ternäre Schreibweise korrekt umsetzt, dann bedeutet das nicht mehr und 
nicht weniger als: der Compiler taugt nix.
#7094481
Lesenswert?

Eggsbert schrieb:
> Wenn AVR:
>> von Docureader (Gast)  10.06.2022 16:28
>
> PORTA.OUT = __builtin_avr_insert_bits(0xfffff2ff, b1, PORTA.OUT);
> Wird in 4 Maschinen-Befehlen (in, lds, bst, out) ausgeführt.
> Kürzer, schneller, eleganter und edler geht es nicht.
> Man muss nur wissen wie.

Naja, so RMW-Sequenzen müssen ggf. auch noch gegen Interrupts gesperrt 
werden.

Da es ja um AVR zu gehen scheint, könnte man auch vermuten, dass es sich 
dabei um die moderneren AVRs haben, bei denen man gar kein RMW braucht, 
sondern das über die CLR / SET-Register kürzer und atomar machen kann.
Gast #7094578
Lesenswert?

Ein extremes Beispiel findet sich in den Quelltexten des Apple 2 
Betriebssystems.

In dem Assemblercode finden sich abstruse and/or/xor Verknüpfungen, bei 
denen vollkommen undurchschaubare Ergebnisse übrig bleiben. Und auf 
dieses Kuddelmuddel werden dann weiter Tests gemacht - bei denen niemand 
mehr durchblickt.

Lieber vollkommen undurchschaubare Verknüpfungen als ein Sprungbefehl zu 
viel.

Der praktische Nutzen? BS und Anwendungsprogramm passen in 64 Kilobyte.
Gast #7094637
Lesenswert?

Georg M. schrieb:
> Kann man dieses Beispiel irgendwie ohne if() umsetzen?
> (ist nicht sehr wichtig, nur eine theoretische Frage)
1
// Eingangsbits definieren (dabei auf Endianity achten!):
2
typedef struct
3
{
4
  uint8_t TasterGruen:1,
5
          TasterRot:1,
6
          TasterBlau:1,
7
          :5;            // unbenutze Bits
8
} tstTaster;
9
// Ausgangsbits definieren:
10
typedef struct
11
{
12
  uint8_t LEDGruen:1,
13
          :5,            // unbenutze Bits
14
          LEDRot:1,
15
          LEDGelb:1;
16
} tstLampen;
17
// Danach einfach zuweisen, den Rest erledigt der Compiler:
18
((tstLampen*) &PORT_OUT)->LEDRot = ((tstTaster*) &PORT_IN)->TasterRot;
Man kann das auch noch eleganter handhaben, indem man die Port-Pointer 
vorher festlegt, z. B.:
1
tstTaster *pTaster = (tstTaster*) &PORT_IN;
2
tstLampen *pLampen = (tstLampen*) &PORT_OUT;
3
// ...
4
pLampen->LEDRot = pTaster->TasterRot;
Ein "volatile" für den Eingangsport sollte für alle Fälle (wiederholter 
Zugriff auf die Ports) auch berücksichtigt werden.
Gast #7094944
Lesenswert?

Wilhelm M. schrieb:
> Da es ja um AVR zu gehen scheint, könnte man auch vermuten, dass es sich
> dabei um die moderneren AVRs haben, bei denen man gar kein RMW braucht,
> sondern das über die CLR / SET-Register kürzer und atomar machen kann.

Wenn nur ein toggle/xor gewünscht ist, könnte man das selbst bei älteren 
AVR (z.B. ATmega644) sehr effizient machen: Bei denen kann man durch 
Schreiben des PINx Registers die 1-bits im PORTx Register toggeln.
1
PINB = (1<<PB1);
2
//    ldi r18, 2
3
//    out PINB, r18
4

5
// Equivalent to:
6
// PORTB ^= (1<<PB1)
7
//    in  r18, PORTB
8
//    ori r18, 2       ; sbr r18, 1
9
//    out PORTB, r18

Als kleine Spezialität wird sbi nicht als RMW des Registers 
ausgeführt, sondern ist ein echter Bit-Befehl und führt direkt zu 
bit-xor im PORTx Register. Das ist vielleicht nicht jedem auf Anhieb 
offensichtlich...
1
PINB |= (1<<PB1);
2
//    sbi PINB, 1
3

4
// Equivalent to:
5
// PORTB ^= (1<<PB1)
6
//    in  r18, PORTB
7
//    ori r18, 2       ; sbr r18, 1
8
//    out PORTB, r18
9

10
// NOT equivalent to:
11
// PORTB ^= PINB | (1<<PB1)
12
//    in  r18, PINB
13
//    ori r18, 2       ; sbr r18, 1
14
//    in  r19, PORTB
15
//    or  r19, r18
16
//    out PORTB, r19

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren