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

OP #7093359
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
Falls der Port auch mal kurz auf 0 gehen darf:
immer mit &= clearen, dann geshiftetes ergebnis der if bedingung wieder 
"rein-Odern"...
Gast
Mario M. schrieb:
>
1
tmp = PORTA.OUT & 0xf7;
2
> PORTA.OUT = tmp | b1 & 8;

Das scheint mir gute Lösung zu sein.

Ein Compiler in wirklich hoher Optimierungsstufe sollte allerdings die 
Vorgabe-Lösung des TE im Idealfall exakt genauso hinkriegen! Vielleicht 
sogar codeidentisch.

Gruss
Gast
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
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
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
Erich schrieb:
> Hmm,
> und was hat jetzt diese deine Erläuterung
> zu tun mit
> der Fragestellung (= erster Beitrag).

Das er das Programmieren erfunden hat. Alle anderen sind in seinen Augen 
Idioten. Er ist der Macker. Nur er. Ohne Helm und ohne Gurt.
Kot Er.
Gast
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
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
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
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
> 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
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, ...
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
>   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).
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)
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
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
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
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
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
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
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.
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
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
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
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

Eine eigene Frage? Dieser Thread ist über 4 Jahre alt. Stell sie in einem eigenen Thread – dort lesen mehr Leute mit, und sie wird öfter und schneller beantwortet.

Neuen Thread starten

Eine Lösung, Ergänzung oder Rückmeldung zu diesem Thread? Dann antworte hier.

Formatierung
  • [c]C-Code[/c]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] – Link zu einem Artikel
  • Verweis auf einen anderen Beitrag: Rechtsklick auf dessen Beitrags-ID (#8078628), „Adresse kopieren“, in den Text einfügen

Mehr zur Formatierung

Längeren Sourcecode als Anhang, nicht in den Text. JPG nur für Fotos und Scans; Zeichnungen und Screenshots als PNG oder GIF. Siehe Bildformate.

Schon registriert? Anmelden

Wird nicht angezeigt. Wir schicken dir einen Link; sobald du damit bestätigst, erscheint dein Beitrag.

Mit dem Abschicken legst du einen kostenlosen Account an und erkennst die Nutzungsbedingungen und die Datenschutzerklärung an.