Forum: Mikrocontroller und Digitale Elektronik Bitfeld zuweisen: Warum optimiert der Compiler nicht?


von Walter T. (nicolas)


Lesenswert?

Hallo zusammen,

ich habe eine 16-Bit-Variable und ein 16 Bit breites Bitfeld. Die 
Reihenfolge im Bitfeld entspricht der Bit-Reihenfolge unter 
Berücksichtigung der Endianness.

Die Definition des Bitfelds sieht so aus:
1
    typedef struct Inputs_s
2
    {
3
        unsigned int mpgKey :1;         /**< Funktionstaste am Handrad gedrueckt */
4
        unsigned int dirRight :1;       /**< Richtungswahlschalter rechts */
5
        unsigned int dirLeft :1;        /**< Richtungswahlschalter links */
6
        unsigned int locknutswitch :1;  /**< Schalter-Status Schlossmutterschalter */
7
        unsigned int eStopSignal :1;    /**< Not-Halt-Signal */
8
        unsigned int key2 :1;           /**< Taster 2 an Geraet */
9
        unsigned int key1 :1;           /**< Taster 1 an Geraet */
10
        unsigned int key0 :1;           /**< Drehgeber-Taster */
11
        unsigned int reserved : 6;      /**< Unbelegt */
12
        unsigned int driveStatusOk :1;  /**< Status-Signal Schrittmotor-Endstufe */
13
        unsigned int chargePumpOk :1;   /**< Ladungspumpensignal */
14
    } Inputs_t;
und die Zuweisung so:
1
    uint_fast16_t input = input_normalize(pushbutton_get());
2
    Out.Inputs.key0 =          !!(input & SWITCH_KEY0);
3
    Out.Inputs.key1 =          !!(input & SWITCH_KEY1);
4
    Out.Inputs.key2 =          !!(input & SWITCH_KEY2);
5
    Out.Inputs.eStopSignal =   !!(input & SWITCH_ESTOP);
6
    Out.Inputs.locknutswitch = !!(input & SWITCH_LOCKNUT);
7
    Out.Inputs.dirLeft =       !!(input & SWITCH_DIRECTION_LEFT);
8
    Out.Inputs.dirRight =      !!(input & SWITCH_DIRECTION_RIGHT);
9
    Out.Inputs.mpgKey =        !!(input & SWITCH_KEY_MPG);
10
    Out.Inputs.chargePumpOk =  !!(input & SWITCH_CHARGE_PUMP_STATUS);
11
    Out.Inputs.driveStatusOk = !!(input & SWITCH_DRVx_STATUS);
12
    Out.Inputs.reserved = 0;
Es entsteht daraus das hier:
1
        /* Eingang normalisieren */
2
        return in ^ ~x;
3
 8008238:  f480 707e   eor.w  r0, r0, #1016  ; 0x3f8
4
 800823c:  43c0        mvns  r0, r0
5
    Out.Inputs.key0 =          !!(input & SWITCH_KEY0);
6
 800823e:  4a83        ldr  r2, [pc, #524]  ; (800844c <eg_slowTask+0x228>)
7
 8008240:  f892 302c   ldrb.w  r3, [r2, #44]  ; 0x2c
8
 8008244:  f360 13c7   bfi  r3, r0, #7, #1
9
    Out.Inputs.key1 =          !!(input & SWITCH_KEY1);
10
 8008248:  f3c0 0140   ubfx  r1, r0, #1, #1
11
 800824c:  f361 1386   bfi  r3, r1, #6, #1
12
    Out.Inputs.key2 =          !!(input & SWITCH_KEY2);
13
 8008250:  f3c0 0180   ubfx  r1, r0, #2, #1
14
 8008254:  f361 1345   bfi  r3, r1, #5, #1
15
    Out.Inputs.eStopSignal =   !!(input & SWITCH_ESTOP);
16
 8008258:  f3c0 01c0   ubfx  r1, r0, #3, #1
17
 800825c:  f361 1304   bfi  r3, r1, #4, #1
18
    Out.Inputs.locknutswitch = !!(input & SWITCH_LOCKNUT);
19
 8008260:  f3c0 1100   ubfx  r1, r0, #4, #1
20
 8008264:  f361 03c3   bfi  r3, r1, #3, #1
21
    Out.Inputs.dirLeft =       !!(input & SWITCH_DIRECTION_LEFT);
22
 8008268:  f3c0 1140   ubfx  r1, r0, #5, #1
23
 800826c:  f361 0382   bfi  r3, r1, #2, #1
24
    Out.Inputs.dirRight =      !!(input & SWITCH_DIRECTION_RIGHT);
25
 8008270:  f3c0 1180   ubfx  r1, r0, #6, #1
26
 8008274:  f361 0341   bfi  r3, r1, #1, #1
27
    Out.Inputs.mpgKey =        !!(input & SWITCH_KEY_MPG);
28
 8008278:  f3c0 11c0   ubfx  r1, r0, #7, #1
29
 800827c:  f361 0300   bfi  r3, r1, #0, #1
30
 8008280:  f882 302c   strb.w  r3, [r2, #44]  ; 0x2c
31
    Out.Inputs.chargePumpOk =  !!(input & SWITCH_CHARGE_PUMP_STATUS);
32
 8008284:  f3c0 2100   ubfx  r1, r0, #8, #1
33
 8008288:  f892 302d   ldrb.w  r3, [r2, #45]  ; 0x2d
34
 800828c:  f361 13c7   bfi  r3, r1, #7, #1
35
    Out.Inputs.driveStatusOk = !!(input & SWITCH_DRVx_STATUS);
36
 8008290:  f3c0 2040   ubfx  r0, r0, #9, #1
37
 8008294:  f360 1386   bfi  r3, r0, #6, #1
38
    Out.Inputs.reserved = 0;
39
 8008298:  f36f 0305   bfc  r3, #0, #6
40
 800829c:  f882 302d   strb.w  r3, [r2, #45]  ; 0x2d
Man sieht: Es wird jedes Bit einzeln geprüft und zugewiesen, und man 
sieht auch, dass der Bit-Offset immer gleich ist. Ich hätte erwartet, 
dass der Compiler daraus eine Maskierung und eine Zuweisung macht.

Was hindert ihn daran?

An dieser "Mikro-Optimierung" hängt nichts. Aber ich will es trotzdem 
verstehen.

von MaWin (Gast)


Lesenswert?

Da musst du den Hersteller des unbekannten Compilers fragen.

von Walter T. (nicolas)


Lesenswert?

MaWin schrieb:
> Da musst du den Hersteller des unbekannten Compilers fragen.

Würde Dein Lieblings-Compiler das optimieren? Meinen kannst Du ja nicht 
ausprobieren.


Ich habe einen ARM-GCC (none-eabi) 5.4.1 in der Optimierungsstufe -O1, 
aber ich kann ja von niemandem verlangen, mein Build-System 
nachvollziehen zu können.

von Peter D. (peda)


Lesenswert?

Walter T. schrieb:
> Ich hätte erwartet,
> dass der Compiler daraus eine Maskierung und eine Zuweisung macht.

Macht er doch, je Bit. Besser gehts nicht.
So eine wahllose Umsortiererei ist nunmal aufwendig. Warum muß das 
überhaupt sein?

von Walter T. (nicolas)


Lesenswert?

Peter D. schrieb:
> Warum muß das
> überhaupt sein?

Meinst Du das Bitfeld? Das Bitfeld wird jetzt ein wenig durchgereicht 
(call by value), deswegen bevorzuge ich es gegenüber einem großen 
boolhaltigen struct. Und dann wird ein wenig boolesche Logik darauf (und 
ein paar andere Bitfelder) angewendet und die Zustandsmaschine damit 
gefüttert. Da ist die Bitfeld-Schreibweise einfach übersichtlicher, als 
in jedem IF-Konstrukt mit der Maske zu verunden.

Ich könnte auch die bool-Variablen erst an den Endpunkten des 
Durchreichens erzeugen, aber ich brauche die gleichen Werte an mehreren 
Stellen, deswegen erschien mir das praktischer.

Peter D. schrieb:
> So eine wahllose Umsortiererei ist nunmal aufwendig.

Wieso wahllos? Der Offset ist überall gleich. Die Bit-Reihenfolge ist im 
uint16 und im Bitfeld doch gleich.

: Bearbeitet durch User
von MaWin (Gast)


Lesenswert?

Walter T. schrieb:
> aber ich kann ja von niemandem verlangen, mein Build-System
> nachvollziehen zu können.

Warum fragst du dann genau danach?

von Walter T. (nicolas)


Lesenswert?

Walter T. schrieb:
> Die Bit-Reihenfolge ist im
> uint16 und im Bitfeld doch gleich.

Nachtrag: Das erste Makro SWITCH_KEY0 hat den Wert 1 und alle folgenden 
Makros jeweils den doppelten Wert. Aber ich dachte, da sei aus dem 
Assembler-Listing ersichtlich.

von Peter D. (peda)


Lesenswert?

Walter T. schrieb:
> Da ist die Bitfeld-Schreibweise einfach übersichtlicher

Das geht aber genauso gut, wenn Du die Bitreihenfolge einfach so läßt, 
wie sie pushbutton_get() liefert.

von Walter T. (nicolas)


Lesenswert?

Peter D. schrieb:
> Das geht aber genauso gut, wenn Du die Bitreihenfolge einfach so läßt,
> wie sie pushbutton_get() liefert.

Das war auch mein Plan. Wo habe ich mir da einen Fehler geleistet? Der 
ARM Cortex M3 wird im little-Endian-Modus betrieben.

(Einen Fehler in der Bit-Reihenfolge auszuschließen war auch der einzige 
Grund, mir das ASM-Listing überhaupt anzusehen. Aber ich sehe den 
Schnitzer immer noch nicht.)

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?

Walter T. schrieb:
> Die Bit-Reihenfolge ist im
> uint16 und im Bitfeld doch gleich.

Im Assembler sieht aber alles umgedreht aus:
1 -> 6
2 -> 5
3 -> 4
usw.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Walter T. schrieb:
> Wieso wahllos? Der Offset ist überall gleich. Die Bit-Reihenfolge ist im
> uint16 und im Bitfeld doch gleich.

Nein, sie ist umgedreht.

von Rolf M. (rmagnus)


Lesenswert?

Vermutlich ist das einfach so ein spezieller Fall (Eine 16-Bit-Variable, 
die  im Prinzip einfach in eine andere 16-Bit-Variable kopiert wird, 
aber nicht am Stück, sondern Bit für Bit einzeln), dass sich keiner die 
Mühe gemacht hat, so eine Optimierung einzubauen. Warum nimmst du nicht 
einfach einen Cast oder memcpy?

von Walter T. (nicolas)


Lesenswert?

Yalu X. schrieb:
> Nein, sie ist umgedreht.

Peter D. schrieb:
> Im Assembler sieht aber alles umgedreht aus:

Ihr habt Recht. Ich habe das falsche struct und das falsche Listing 
hochgeladen. Dabei hatte ich schon meinen Kaffee. Das, was ihr oben 
seht, war eine Verzweiflungstat.
1
    typedef struct Inputs_s
2
    {
3
        unsigned int chargePumpOk :1;   /**< Ladungspumpensignal */
4
        unsigned int driveStatusOk :1;  /**< Status-Signal Schrittmotor-Endstufe */
5
        unsigned int reserved : 6;      /**< Unbelegt */
6
        unsigned int key0 :1;           /**< Drehgeber-Taster */
7
        unsigned int key1 :1;           /**< Taster 1 an Geraet */
8
        unsigned int key2 :1;           /**< Taster 2 an Geraet */
9
        unsigned int eStopSignal :1;    /**< Not-Halt-Signal */
10
        unsigned int locknutswitch :1;  /**< Schalter-Status Schlossmutterschalter */
11
        unsigned int dirLeft :1;        /**< Richtungswahlschalter links */
12
        unsigned int dirRight :1;       /**< Richtungswahlschalter rechts */
13
        unsigned int mpgKey :1;         /**< Funktionstaste am Handrad gedrueckt */
14
    } Inputs_t;
15
16
        /* Eingang normalisieren */
17
        return in ^ ~x;
18
 8008238:  f480 707e   eor.w  r0, r0, #1016  ; 0x3f8
19
 800823c:  43c0        mvns  r0, r0
20
    Out.Inputs.key0 =          !!(input & SWITCH_KEY0);
21
 800823e:  4a83        ldr  r2, [pc, #524]  ; (800844c <eg_slowTask+0x228>)
22
 8008240:  f892 302d   ldrb.w  r3, [r2, #45]  ; 0x2d
23
 8008244:  f360 0300   bfi  r3, r0, #0, #1
24
    Out.Inputs.key1 =          !!(input & SWITCH_KEY1);
25
 8008248:  f3c0 0140   ubfx  r1, r0, #1, #1
26
 800824c:  f361 0341   bfi  r3, r1, #1, #1
27
    Out.Inputs.key2 =          !!(input & SWITCH_KEY2);
28
 8008250:  f3c0 0180   ubfx  r1, r0, #2, #1
29
 8008254:  f361 0382   bfi  r3, r1, #2, #1
30
    Out.Inputs.eStopSignal =   !!(input & SWITCH_ESTOP);
31
 8008258:  f3c0 01c0   ubfx  r1, r0, #3, #1
32
 800825c:  f361 03c3   bfi  r3, r1, #3, #1
33
    Out.Inputs.locknutswitch = !!(input & SWITCH_LOCKNUT);
34
 8008260:  f3c0 1100   ubfx  r1, r0, #4, #1
35
 8008264:  f361 1304   bfi  r3, r1, #4, #1
36
    Out.Inputs.dirLeft =       !!(input & SWITCH_DIRECTION_LEFT);
37
 8008268:  f3c0 1140   ubfx  r1, r0, #5, #1
38
 800826c:  f361 1345   bfi  r3, r1, #5, #1
39
    Out.Inputs.dirRight =      !!(input & SWITCH_DIRECTION_RIGHT);
40
 8008270:  f3c0 1180   ubfx  r1, r0, #6, #1
41
 8008274:  f361 1386   bfi  r3, r1, #6, #1
42
    Out.Inputs.mpgKey =        !!(input & SWITCH_KEY_MPG);
43
 8008278:  f3c0 11c0   ubfx  r1, r0, #7, #1
44
 800827c:  f361 13c7   bfi  r3, r1, #7, #1
45
 8008280:  f882 302d   strb.w  r3, [r2, #45]  ; 0x2d
46
    Out.Inputs.chargePumpOk =  !!(input & SWITCH_CHARGE_PUMP_STATUS);
47
 8008284:  f3c0 2100   ubfx  r1, r0, #8, #1
48
 8008288:  f892 302c   ldrb.w  r3, [r2, #44]  ; 0x2c
49
 800828c:  f361 0300   bfi  r3, r1, #0, #1
50
    Out.Inputs.driveStatusOk = !!(input & SWITCH_DRVx_STATUS);
51
 8008290:  f3c0 2040   ubfx  r0, r0, #9, #1
52
 8008294:  f360 0341   bfi  r3, r0, #1, #1
53
    Out.Inputs.reserved = 0;
54
 8008298:  f36f 0387   bfc  r3, #2, #6
55
 800829c:  f882 302c   strb.w  r3, [r2, #44]  ; 0x2c

Rolf M. schrieb:
> Warum nimmst du nicht
> einfach einen Cast oder memcpy?

Die ausführliche Schreibweise oben ist portabel. So 
Optimierungs-Verzweifelt, dass ich implementationsabhängigen Code 
einbauen will, bin ich an dieser Stelle nicht. Es ist ja eher ein 
Kuriosum als ein echtes Problem.

: Bearbeitet durch User
von Nils (Gast)


Lesenswert?

Kannst ja beim GCC Bugtracker ein "missed optimization opportunity" Bug 
einstellen. Hab ich vor 10 Jahren mal für eine Byte Permutation für x86 
gemacht. Hat keine Woche gedauert, dann war das im GCC drin.

von Peter D. (peda)


Angehängte Dateien:

Lesenswert?

Hier mal ein Ausschnitt aus einer älteren Steuerung (ATmega8):
Die Eingänge werden über 74HC165 eingelesen, die Ausgänge über 74HC595 
ausgegeben und die LEDs über MAX7219.
Im RAM befinden sich quasi die virtuellen Pins. Über Macros werden sie 
dann so definiert, wie sie sich aus dem Schaltplan ergeben. Eine 
Umsortierung ist daher nicht nötig, da immer nur mit den Symbolen 
gearbeitet wird.
1
void hfc_actions ( void )    // hfc_status
2
{
3
  LED_HFC_REM = 0;
4
  LED_HFC_LOC = 0;
5
  LED_HFC_HFM = 0;
6
  LED_HFC_OFF = 0;
7
8
  if( !HFC_PWR )          // HFC on ?
9
    LED_HFC = 0;                        // HFC not avail
10
11
  if( LED_HFC ){      // key HFC on ?
12
13
    if(( PLC_PWR       // PLC on
14
    &&  PLC_REM)      // and in remote ?
15
    || LED_STC ){      // or STC    added 16.12.09
16
      LED_HFC_REM = 1;
17
18
      if( !LED_STC ){      // no STC
19
  if(  PLC_CONNECT
20
  &&  PLC_DBM ){
21
    LED_HFC_HFM = 1;
22
  }else{
23
    LED_HFC_OFF = 1;
24
  }
25
      }
26
    }else{
27
      LED_HFC_LOC = 1;
28
29
      if( !LED_STC ){      // no STC
30
  if(  HFC_HFON ){
31
    LED_HFC_HFM = 1;
32
  }else{
33
    LED_HFC_OFF = 1;
34
  }
35
      }
36
    }
37
  }
38
}

Anbei die Header.

von Walter T. (nicolas)


Lesenswert?

Peter D. schrieb:
> Hier mal ein Ausschnitt aus einer älteren Steuerung (ATmega8)

Den Entwurf habe ich auch in Erwägung gezogen und an dieser Stelle 
verworfen. Hier will ich mal Eingänge nicht global haben, sondern 
definiert per Funktionsaufrufen übergeben. (Im Hintergrund stellt dann 
eine Doppelpufferstrategie sicher, dass die Zustände jeweils konsistent 
sind. Für die Eingänge selbst ist das auch gar nicht mal so wichtig, 
aber ich will keine zwei unterschiedlichen Mechanismen haben, um 
zwischen den gleichen zwei ISRs und main() zu kommunizieren.)

Aus Neugier habe ich es gerade noch ausprobiert, aber 
__attribute__((_packed_)) ändert nichts am obenstehenden Kuriosum.

Sollte mich die "verpasste Gelegenheit zur Optimierung" dennoch mal 
stören, reiche ich einfach die 16-Bit-Variable durch und lagere die 
"Dekodierung" in ein Bitfeld in eine Static-Inline-Funktion aus. Wenn 
das Bitfeld dann ausschließlich in der Funktion verwendet wird, in der 
es auch erzeugt wird, wird es wegoptimiert und es bleiben die erwarteten 
Operationen zurück.

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

Walter T. schrieb:
> Rolf M. schrieb:
>> Warum nimmst du nicht
>> einfach einen Cast oder memcpy?
>
> Die ausführliche Schreibweise oben ist portabel.

Man könnte entsprechende static_asserts einbauen, um zu prüfen, dass die 
Bitpositionen alle richtig sind. Dann wäre auch der Fehler, den du 
gemacht hast, gleich aufgefallen ;-)

> So Optimierungs-Verzweifelt, dass ich implementationsabhängigen Code
> einbauen will, bin ich an dieser Stelle nicht. Es ist ja eher ein
> Kuriosum als ein echtes Problem.

Ok, akzeptiert.

von Peter D. (peda)


Lesenswert?

Walter T. schrieb:
> Hier will ich mal Eingänge nicht global haben, sondern
> definiert per Funktionsaufrufen übergeben. (Im Hintergrund stellt dann
> eine Doppelpufferstrategie sicher, dass die Zustände jeweils konsistent
> sind.)

Man kann es mit der Kapselung aber auch übertreiben.
Bei nur 16 Eingängen sehe ich keinerlei Gefahr, den Überblick zu 
verlieren, wenn sie global sind. Im Gegenteil, bei einer SPS sind ja 
auch alle Pins global.

von A. S. (Gast)


Lesenswert?

Walter T. schrieb:
> in der Optimierungsstufe -O1

Also nur zur Sicherheit: das ist die höchste Optimierungsstufe und weder 
Out noch Out.Input noch irgendwas anderes in Out sind irgendwie 
volatile.

von Peter D. (peda)


Lesenswert?

Ich mag generell keine Bitfelder und setze sie auch nicht direkt ein, da 
sie nicht portabel sind. Auch kann man nicht leicht erkennen, welches 
Bit welche Nummer hat. Man muß es im Bitfeld umständlich abzählen und 
die Order kennen.
Es können sich also leicht Fehler einschleichen.

Das Macro in der sbit.h ist die Ausnahme und nur für den AVR-GCC 
geprüft.
Mit der Benutzung des Macros ist dann wieder die Bitnummer sicher zu 
erkennen, da direkt in der Definition der Bitvariablen angegeben.

Professionelle Header, z.B. für den LPC4357 im IAR benutzen immer nur 
Bitnummern oder Masken, aber keine Bitfelder.

von Walter T. (nicolas)


Lesenswert?

Peter D. schrieb:
> Man kann es mit der Kapselung aber auch übertreiben.
> Bei nur 16 Eingängen sehe ich keinerlei Gefahr, den Überblick zu
> verlieren, wenn sie global sind.

Das ist eine Frage der Blickrichtung. Aus der Blickrichtung "da will 
jemand Eingänge behandeln" muss sich die Folgerung, dass hier etwas 
Einfaches sehr umständlich gemacht wird, aufdrängen. Und diese 
Blickrichtung ergibt sich aus dem Threadverlauf zwangsläufig.

Jetzt eine andere Blickrichtung: Ich habe ein gutes Dutzend Variablen, 
davon einige 64 Bit breit, die konsistent zwischen drei "Tasks" 
ausgetauscht werden müssen. Dafür ist ein Mechanismus implementiert. 
Jetzt habe ich 1x16 Bit, bei denen das nicht erforderlich ist. Da lohnt 
es sich nicht, einen Extra-Mechanismus zu schreiben. Die werden einfach 
dazugepackt.

A. S. schrieb:
> Also nur zur Sicherheit

Es ist nur -O1, der Lebenszyklus von Out sieht so aus:
1
void SlowTask(void)
2
{
3
    static SlowTaskOutput_t Out;
4
   // Dem struct Out werden jede Menge Einzelwerte zugewiesen und nie gelesen
5
   if( blabla )
6
      SlowTaskOutputBuffer[1] = Out;
7
   else
8
      SlowTaskOutputBuffer[2] = Out;
9
}
Out ist static, weil nicht in jedem Aufruf der Funktion alles 
geschrieben werden muss. SlowTaskOutputBuffer[] ist natürlich volatile, 
aber wird ja nur per Wert überschrieben. input ist eine lokale Variable.

Ich sehe nichts, was eine Optimierung verhindern würde.

Peter D. schrieb:
> Ich mag generell keine Bitfelder und setze sie auch nicht direkt ein

Ich mag sie generell auch nicht und erst Recht nicht, um Register zu 
beschreiben. Aber hier schien einer der wenigen Fälle, wo sie passen und 
keine Falle beinhalten. (Wenn man von der "Falle" absieht, dass man im 
Gegensatz zu bool aufpassen muss, sie mit 1 oder 0 zu beschreiben und 
nicht blos mit logischen Ausdrücken.)

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?

Walter T. schrieb:
> Aber hier schien einer der wenigen
> Fällen, wo sie passen und keine Falle beinhalten.

Bezeichnend ist aber, daß Du erst einen falschen Code gepostet hast und 
es Dir nicht aufgefallen ist. Erst im Assemblerlisting hat man sich 
gewundert.
Die Falle ist also real und das Meiden von Bitfeldern ist völlig 
berechtigt.

von A. S. (Gast)


Lesenswert?

Walter T. schrieb:
> Es ist nur -O1

Dann nimm doch spaßeshalber Mal die höchste. Und uint16_t als Datentyp.

Und fang damit an, alle Bits in einer Dummy-Funktion auf 1 zu setzen.

von Walter T. (nicolas)


Lesenswert?

A. S. schrieb:
> Dann nimm doch spaßeshalber Mal die höchste. Und uint16_t als Datentyp.

Mit -O3 wird die Funktionalität innerhalb der Funktion im 
Assembler-Listing etwas auseinandergerissen, dass es zu unübersichtlich 
ist, um hier gepostet zu werden. (In der Funktion passiert ja noch 
mehr.) Es wird aber immer noch jedes Bit einzeln "kopiert".

Peter D. schrieb:
> Bezeichnend ist aber, daß Du erst einen falschen Code gepostet hast und
> es Dir nicht aufgefallen ist.

Hättest Du "falschen" in Anführungszeichen gesetzt, stimmte ich Dir zu. 
Der Code war "falsch", aber nicht falsch. Er hat ja für jeden denkbaren 
Wert das exakt erwartete Ergebnis erzeugt, das auch im weiteren 
Programmablauf passend weiterverarbeitet wurde. Nur hat ihn der Compiler 
nicht optimiert. Aber das macht er mit dem "richtigen" Code ja auch 
nicht.

: Bearbeitet durch User
von W.S. (Gast)


Lesenswert?

Walter T. schrieb:
> ich habe eine 16-Bit-Variable und ein 16 Bit breites Bitfeld. Die
> Reihenfolge im Bitfeld entspricht der Bit-Reihenfolge unter
> Berücksichtigung der Endianness.

Bedenke mal, daß die ARM-Cortex Architektur keine Bitoperationen bzw. 
Bitadressierungen kennt und in C ebenfalls kein Boolean Typ existiert. 
Mit einer Programmiersprache, die sowas nur über Konventionen 
realisieren kann (0 ist false, 1 ist true) zusammen mit einer 
Zielarchitektur, die einzelne Bits nicht kennt sondern lediglich bei 
einigen Chiptypen für einige Hardware-Bits (zumeist Portzustände) 
passende Bitfelder (als char oder bis zu long) vorhält, ist das Erwarten 
von ausgeknufften Optimierungen eine zu kühne Erwartung.

Andere Architekturen wie z.B. die PIC16 kennen Bitoperationen, 
allerdings nur unter der einschränkenden Bedingung, daß sie dazu noch 
die Adresse des Bytes benötigen, welches das betr. Bit enthält.

Aber bei Arhitekturen wie Arm oder Mips würde ich genauso wie Peter um 
Bitfelder einen großen Bogen machen.

W.S.

von PittyJ (Gast)


Lesenswert?

Ich finde das Vorgehen des Compilers OK.
Es kann doch auch durchaus sein, dass die zeitliche Reihenfolge wichtig 
ist.
Es könnten IOs dranhängen, die zeitlich nacheinander geschaltet werden 
müssen. Woher sollte der Compiler wissen, dass das auch parallel gehen 
kann?

Wenn du eine gleichzeitige Zuweisung haben möchtest, dann brauchst du 
ein FPGA. Da ist die Reihenfolge der Zuweisungen egal, es wird immer mit 
dem nächsten Takt alles gleichzeitig gesetzt.

von (prx) A. K. (prx)


Lesenswert?

W.S. schrieb:
> Bedenke mal, daß die ARM-Cortex Architektur keine Bitoperationen bzw.
> Bitadressierungen kennt

Bitfeld könne die Cortex M0 nicht, die mit Thumb2 aber schon. Der Code 
vom TE ist Cortex.

Einzelbitadressierung per Bitbanding gibts auch im RAM, wieder ausser M0 
(ohne +).

: Bearbeitet durch User
von Walter T. (nicolas)


Lesenswert?

PittyJ schrieb:
> Woher sollte der Compiler wissen, dass das auch parallel gehen
> kann?

Er nimnmt das standardmäßig an. Alles, wo das NICHT der Fall ist, muss 
markiert werden. (Stichwort -> volatile)

W.S. schrieb:
> Bedenke mal, daß die ARM-Cortex Architektur keine Bitoperationen bzw.
> Bitadressierungen kennt und in C ebenfalls kein Boolean Typ existiert.

Die Logik verstehe ich nicht. Kennte der Befehlssatz Bitoperationen, 
könnte der Compiler sie in einer Schleife einsetzen (und würde das 
Optimierungspotenzial evtl. übersehen, solange er kein Loop-Unrollig 
macht). So muss er für jedes Bit beim lesen eine Maske berechnen und 
beim Schreiben die selbe Maske berechnen und das Ganze achtmal für 
jeweils die gleichen zwei Adressen. Da hätte ich eher erwartet, dass da 
ein Penalty-Faktor draufliegt, der eine Optimierung erst anstößt.

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

Peter D. schrieb:
> Ich mag generell keine Bitfelder und setze sie auch nicht direkt ein, da
> sie nicht portabel sind.

Bitfelder sind durchaus portabel, wenn man sie so einsetzt, wie sie 
gedacht sind, also nicht, um eine bestimmte Schnittstelle umzusetzen, 
sondern lediglich um Platz zu sparen. Und wenn man das im Hinterkopf 
behält, braucht man sie sowieso nur sehr selten.

Walter T. schrieb:
> (Wenn man von der "Falle" absieht, dass man im
> Gegensatz zu bool aufpassen muss, sie mit 1 oder 0 zu beschreiben und
> nicht blos mit logischen Ausdrücken.)

Warum? Übrigens: Ein Bitfeld darf auch vom Typ bool sein.

W.S. schrieb:
> Bedenke mal, daß die ARM-Cortex Architektur keine Bitoperationen bzw.
> Bitadressierungen kennt und in C ebenfalls kein Boolean Typ existiert.

Hrmpf, warum musst du nur immer so einen Unsinn über C schreiben. Ein 
boolescher Typ existiert in Standard-C seit 22 Jahren. Wie das 
allerdings bei dieser Optimierung helfen soll, ist unklar.

PittyJ schrieb:
> Ich finde das Vorgehen des Compilers OK.
> Es kann doch auch durchaus sein, dass die zeitliche Reihenfolge wichtig
> ist.

Davon muss der Compiler nur ausgehen, wenn eine der Variablen volatile 
ist.

> Es könnten IOs dranhängen, die zeitlich nacheinander geschaltet werden
> müssen. Woher sollte der Compiler wissen, dass das auch parallel gehen
> kann?

Genau deshalb gibt es volatile - damit der Compiler weiß, dass jeder 
Zugriff auch einzeln durchgeführt werden muss und nicht mit anderen 
zusammengeführt oder in der Reihenfolge umsortiert werden darf.

: Bearbeitet durch User
von Walter T. (nicolas)


Lesenswert?

Rolf M. schrieb:
> Warum?

Weil ansonsten ein ungültiger Wert drin steht.

Rolf M. schrieb:
> Übrigens: Ein Bitfeld darf auch vom Typ bool sein.

Das wusste ich auch nicht.

Und jetzt kommt der interessante Teil: Wenn alle Felder außer "reserved" 
bool sind, werden im Assembler-Listing für die einzelnen Felder 
unterschiedlich viele (2, 3 oder 4) Assembler-Instruktionen fällig. WTF?

von (prx) A. K. (prx)


Lesenswert?

Walter T. schrieb:
> Und jetzt kommt der interessante Teil: Wenn alle Felder außer "reserved"
> bool sind, werden im Assembler-Listing für die einzelnen Felder
> unterschiedlich viele (2, 3 oder 4) Assembler-Instruktionen fällig. WTF?

Bei SWITCH_KEY0 ist das Bit schon an Position 0, weshalb UBFX entfallen 
kann. Alle anderen bestehen durchweg aus UBFX und BFI. Bei den 
gelegentlichen load/store Befehle solltest du einrechnen, dass BFI 
keinen Speicher adressiert, sondern Register. Das Bitfeld wird ausserdem 
als zwei Bytes verarbeitet, nicht als ein Halbwort.

: Bearbeitet durch User
von Walter T. (nicolas)


Lesenswert?

(prx) A. K. schrieb:
> Kann ich in obigem Code nicht finden.
1
    typedef struct Inputs_s
2
    {
3
4
        bool chargePumpOk :1;   /**< Ladungspumpensignal */
5
        bool driveStatusOk :1;  /**< Status-Signal Schrittmotor-Endstufe */
6
        unsigned int reserved : 6;      /**< Unbelegt */
7
8
        bool key0 :1;           /**< Drehgeber-Taster */
9
        bool key1 :1;           /**< Taster 1 an Geraet */
10
        bool key2 :1;           /**< Taster 2 an Geraet */
11
        bool eStopSignal :1;    /**< Not-Halt-Signal */
12
        bool locknutswitch :1;  /**< Schalter-Status Schlossmutterschalter */
13
        bool dirLeft :1;        /**< Richtungswahlschalter links */
14
        bool dirRight :1;       /**< Richtungswahlschalter rechts */
15
        bool mpgKey :1;         /**< Funktionstaste am Handrad gedrueckt */
16
    } Inputs_t;
1
        /* Eingang normalisieren */
2
        return in ^ ~x;
3
 8008238:  f480 707e   eor.w  r0, r0, #1016  ; 0x3f8
4
 800823c:  43c0        mvns  r0, r0
5
    Out.Inputs.key0 =          (input & SWITCH_KEY0);
6
 800823e:  4a82        ldr  r2, [pc, #520]  ; (8008448 <eg_slowTask+0x224>)
7
 8008240:  f892 302d   ldrb.w  r3, [r2, #45]  ; 0x2d
8
 8008244:  f360 0300   bfi  r3, r0, #0, #1
9
    Out.Inputs.key1 =          (input & SWITCH_KEY1);
10
 8008248:  f3c0 0140   ubfx  r1, r0, #1, #1
11
 800824c:  f361 0341   bfi  r3, r1, #1, #1
12
    Out.Inputs.key2 =          (input & SWITCH_KEY2);
13
 8008250:  f3c0 0180   ubfx  r1, r0, #2, #1
14
 8008254:  f361 0382   bfi  r3, r1, #2, #1
15
    Out.Inputs.eStopSignal =   (input & SWITCH_ESTOP);
16
 8008258:  f000 0108   and.w  r1, r0, #8
17
 800825c:  f3c0 05c0   ubfx  r5, r0, #3, #1
18
 8008260:  f365 03c3   bfi  r3, r5, #3, #1
19
    Out.Inputs.locknutswitch = (input & SWITCH_LOCKNUT);
20
 8008264:  f000 0510   and.w  r5, r0, #16
21
 8008268:  f3c0 1e00   ubfx  lr, r0, #4, #1
22
 800826c:  f36e 1304   bfi  r3, lr, #4, #1
23
    Out.Inputs.dirLeft =       (input & SWITCH_DIRECTION_LEFT);
24
 8008270:  f3c0 1e40   ubfx  lr, r0, #5, #1
25
 8008274:  f36e 1345   bfi  r3, lr, #5, #1
26
    Out.Inputs.dirRight =      (input & SWITCH_DIRECTION_RIGHT);
27
 8008278:  f3c0 1e80   ubfx  lr, r0, #6, #1
28
 800827c:  f36e 1386   bfi  r3, lr, #6, #1
29
    Out.Inputs.mpgKey =        (input & SWITCH_KEY_MPG);
30
 8008280:  f000 0e80   and.w  lr, r0, #128  ; 0x80
31
 8008284:  f3c0 1cc0   ubfx  ip, r0, #7, #1
32
 8008288:  f36c 13c7   bfi  r3, ip, #7, #1
33
 800828c:  f882 302d   strb.w  r3, [r2, #45]  ; 0x2d
34
    Out.Inputs.chargePumpOk =  (input & SWITCH_CHARGE_PUMP_STATUS);
35
 8008290:  f3c0 2c00   ubfx  ip, r0, #8, #1
36
 8008294:  f892 302c   ldrb.w  r3, [r2, #44]  ; 0x2c
37
 8008298:  f36c 0300   bfi  r3, ip, #0, #1
38
    Out.Inputs.driveStatusOk = (input & SWITCH_DRVx_STATUS);
39
 800829c:  f3c0 2c40   ubfx  ip, r0, #9, #1
40
 80082a0:  f36c 0341   bfi  r3, ip, #1, #1
41
    Out.Inputs.reserved = 0;
42
 80082a4:  f36f 0387   bfc  r3, #2, #6
43
 80082a8:  f882 302c   strb.w  r3, [r2, #44]  ; 0x2c
Die !! vor dem logischen Ausdruck können jetzt entfallen, weil der 
Compiler beim Bool-Feld den erlaubten Wertebereich kennt. Der Rest ist 
gleich geblieben.

Aber stimmt: Er werden max. drei Befehle pro Bit. Der vierte kommt erst 
beim Byte-Wechsel.

: Bearbeitet durch User
Beitrag #6807090 wurde von einem Moderator gelöscht.
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

W.S. schrieb:
> und in C ebenfalls kein Boolean Typ existiert.

Du solltest dein C-Wissen mal aktualisieren. <stdbool.h> gibt's seit 
mehr als 20 Jahren, auch den Typ _Bool ("bool" konnte man ohne diesen 
Header nicht konfliktfrei zum Standard hinzufügen, da legitime 
Applikationen den Namen zuvor selbst definieren durften).

von (prx) A. K. (prx)


Lesenswert?

Walter T. schrieb:
> Die !! vor dem logischen Ausdruck können jetzt entfallen, weil der
> Compiler beim Bool-Feld den erlaubten Wertebereich kennt. Der Rest ist
> gleich geblieben.

In der !! Version kommen die AND Befehle nicht vor - auf diese Version 
hatte ich mich bezogen.

Gerade bei Bitfeldern sollte man nicht jede maximal mögliche Optimierung 
erwarten. Nicht alles, was man machten könnte, betrachten die 
Compilerbauer als wirklich wichtig. Weshalb dann schon mal ein nicht 
optimales Schema genutzt wird.

: Bearbeitet durch User
von Walter T. (nicolas)


Lesenswert?

(prx) A. K. schrieb:
> In der !! Version kommen die AND Befehle nicht vor - auf diese Version
> hatte ich mich bezogen.

Das liegt am Bool-Datentyp fürs Feld. Ist das Feld  Bool, kommt mit oder 
ohne !! das gleiche Assembly-Listing heraus (sieht man mal von den 
Kommentaren ab).

(prx) A. K. schrieb:
> Weshalb dann schon mal ein nicht
> optimales Schema genutzt wird.

Ich merke es. Naja, was solls. Wenn anfängt zu jucken, mache ich das, 
was ich oben geschrieben habe:

Walter T. schrieb:
> reiche ich einfach die 16-Bit-Variable durch und lagere die
> "Dekodierung" in ein Bitfeld in eine Static-Inline-Funktion aus.

Oder wahrscheinlich noch eher mit einem boolhaltigen struct als 
Rückgabewert.

: Bearbeitet durch User
von Walter T. (nicolas)


Lesenswert?

Nachtrag: Es hat mir doch keine Ruhe gelassen. Ich habe das jetzt 
ausgelagert.
1
/* header.h */
2
    /** Digitale Eingaenge */
3
    typedef struct Inputs_s
4
    {
5
6
        bool chargePumpOk :1;   /**< Ladungspumpensignal */
7
        bool driveStatusOk :1;  /**< Status-Signal Schrittmotor-Endstufe */
8
        unsigned int reserved : 6;      /**< Unbelegt */
9
10
        bool key0 :1;           /**< Drehgeber-Taster */
11
        bool key1 :1;           /**< Taster 1 an Geraet */
12
        bool key2 :1;           /**< Taster 2 an Geraet */
13
        bool eStopSignal :1;    /**< Not-Halt-Signal */
14
        bool locknutswitch :1;  /**< Schalter-Status Schlossmutterschalter */
15
        bool dirLeft :1;        /**< Richtungswahlschalter links */
16
        bool dirRight :1;       /**< Richtungswahlschalter rechts */
17
        bool mpgKey :1;         /**< Funktionstaste am Handrad gedrueckt */
18
    } Inputs_t;
19
20
    /** Eingangs-Vektor normalisieren (d.h. Bit=1 aktiv, Bit=0 inaktiv)
21
     * @param[in] in: Eingangs-Vektor
22
     * @return normalisierter Ausgangs-Vektor */
23
    static_inline uint_fast16_t input_normalize(uint_fast16_t in)
24
    {
25
        /* Active-High-Pins markieren */
26
        uint_fast16_t x = 0;
27
        if( SWITCH_KEY0_ACTIVE_LEVEL )                  x |= SWITCH_KEY0;
28
        if( SWITCH_KEY1_ACTIVE_LEVEL )                  x |= SWITCH_KEY1;
29
        if( SWITCH_KEY2_ACTIVE_LEVEL )                  x |= SWITCH_KEY2;
30
        if( SWITCH_ESTOP_ACTIVE_LEVEL )                 x |= SWITCH_ESTOP;
31
        if( SWITCH_LOCKNUT_ACTIVE_LEVEL )               x |= SWITCH_LOCKNUT;
32
        if( SWITCH_DIRECTION_LEFT_ACTIVE_LEVEL )        x |= SWITCH_DIRECTION_LEFT;
33
        if( SWITCH_DIRECTION_RIGHT_ACTIVE_LEVEL )       x |= SWITCH_DIRECTION_RIGHT;
34
        if( SWITCH_KEY_MPG_ACTIVE_LEVEL)                x |= SWITCH_KEY_MPG;
35
        if( SWITCH_CHARGE_PUMP_STATUS_ACTIVE_LEVEL )    x |= SWITCH_CHARGE_PUMP_STATUS;
36
        if( SWITCH_DRVx_STATUS_ACTIVE_LEVEL )           x |= SWITCH_DRVx_STATUS;
37
38
        /* Eingang normalisieren */
39
        return in ^ ~x;
40
    }
41
42
    /** Schalterstellungen und andere Eingangs-Signale zusammenstellen
43
     *
44
     * @param[in] in: Normalisierte Eingaenge+
45
     * @return Normalisierte Eingaenge als Struct. */
46
    static_inline Inputs_t input_fields(uint16_t in)
47
    {
48
        const Inputs_t Out =
49
        {
50
            .key0 =          !!(in & SWITCH_KEY0),
51
            .key1 =          !!(in & SWITCH_KEY1),
52
            .key2 =          !!(in & SWITCH_KEY2),
53
            .eStopSignal =   !!(in & SWITCH_ESTOP),
54
            .locknutswitch = !!(in & SWITCH_LOCKNUT),
55
            .dirLeft =       !!(in & SWITCH_DIRECTION_LEFT),
56
            .dirRight =      !!(in & SWITCH_DIRECTION_RIGHT),
57
            .mpgKey =        !!(in & SWITCH_KEY_MPG),
58
            .chargePumpOk =  !!(in & SWITCH_CHARGE_PUMP_STATUS),
59
            .driveStatusOk = !!(in & SWITCH_DRVx_STATUS),
60
            .reserved =     (in>>10),
61
        };
62
        return Out;
Entsprechend einfach sieht dann die eigentliche Funktion aus:
1
    uint16_t input = input_normalize(pushbutton_get());
2
    Out.Inputs = input_fields(input);
3
    volatile bool tst = input_fields(input).key0; /* Nur zum testen */
Das Assembler-Listing:
1
      * @return Normalisierte Eingaenge als Struct. */
2
    static_inline Inputs_t input_fields(uint16_t in)
3
    {
4
        const Inputs_t Out =
5
        {
6
            .key0 =          !!(in & SWITCH_KEY0),
7
 8008240:  f000 0c01   and.w  ip, r0, #1
8
 8008244:  f000 0208   and.w  r2, r0, #8
9
 8008248:  b292        uxth  r2, r2
10
 800824a:  f000 0e10   and.w  lr, r0, #16
11
 800824e:  fa1f fe8e   uxth.w  lr, lr
12
 8008252:  f000 0580   and.w  r5, r0, #128  ; 0x80
13
 8008256:  b2ad        uxth  r5, r5
14
    Out.Inputs = input_fields(input);
15
 8008258:  4984        ldr  r1, [pc, #528]  ; (800846c <eg_slowTask+0x248>)
16
 800825a:  f3c0 2800   ubfx  r8, r0, #8, #1
17
 800825e:  f891 302c   ldrb.w  r3, [r1, #44]  ; 0x2c
18
 8008262:  f368 0300   bfi  r3, r8, #0, #1
19
 8008266:  f3c0 2840   ubfx  r8, r0, #9, #1
20
 800826a:  f368 0341   bfi  r3, r8, #1, #1
21
 800826e:  ea4f 2890   mov.w  r8, r0, lsr #10
22
 8008272:  f368 0387   bfi  r3, r8, #2, #6
23
 8008276:  f881 302c   strb.w  r3, [r1, #44]  ; 0x2c
24
 800827a:  f891 302d   ldrb.w  r3, [r1, #45]  ; 0x2d
25
 800827e:  f36c 0300   bfi  r3, ip, #0, #1
26
 8008282:  f3c0 0840   ubfx  r8, r0, #1, #1
27
 8008286:  f368 0341   bfi  r3, r8, #1, #1
28
 800828a:  f3c0 0880   ubfx  r8, r0, #2, #1
29
 800828e:  f368 0382   bfi  r3, r8, #2, #1
30
 8008292:  f112 0800   adds.w  r8, r2, #0
31
 8008296:  bf18        it  ne
32
 8008298:  f04f 0801   movne.w  r8, #1
33
 800829c:  f368 03c3   bfi  r3, r8, #3, #1
34
 80082a0:  f11e 0800   adds.w  r8, lr, #0
35
 80082a4:  bf18        it  ne
36
 80082a6:  f04f 0801   movne.w  r8, #1
37
 80082aa:  f368 1304   bfi  r3, r8, #4, #1
38
 80082ae:  f3c0 1840   ubfx  r8, r0, #5, #1
39
 80082b2:  f368 1345   bfi  r3, r8, #5, #1
40
 80082b6:  f3c0 1880   ubfx  r8, r0, #6, #1
41
 80082ba:  f368 1386   bfi  r3, r8, #6, #1
42
 80082be:  f115 0800   adds.w  r8, r5, #0
43
 80082c2:  bf18        it  ne
44
 80082c4:  f04f 0801   movne.w  r8, #1
45
 80082c8:  f368 13c7   bfi  r3, r8, #7, #1
46
 80082cc:  f881 302d   strb.w  r3, [r1, #45]  ; 0x2d
47
    volatile bool tst = input_fields(input).key0;
48
 80082d0:  f88d c02f   strb.w  ip, [sp, #47]  ; 0x2f

Mein Fazit: Der Compiler lässt sich selbst mit einem dicken Knüppel 
nicht dazu überreden zu sehen, dass hier einfach ein Halbwort kopiert 
werden will.

Aber auch: Für logische Oparation merkt er zumindest, dass er alles 
wegoptimieren kann und von dem Struct als Zwischenergebnis bleibt auch 
bei -O1 nichts mehr übrig. Damit ist das ein brauchbarer Workaround.

: Bearbeitet durch User
von W.S. (Gast)


Lesenswert?

Rolf M. schrieb:
> Hrmpf, warum musst du nur immer so einen Unsinn über C schreiben. Ein
> boolescher Typ existiert in Standard-C seit 22 Jahren.

Das ist allerdings ein verkappter char und kein echter und 
eigenständiger Datentyp.

Und da alle Arm/Cortex Architekturen im Grunde Load-Modify-Store 
Architekturen sind, gibt es auch von der Architektur her keine 
Bitoperationen auf diesen Plattformen, es ist immer das 
Laden-Ändern-Speichern angesagt. Ich habe nicht ohne Grund die PIC16 
angeführt, wo Befehle wie BTFSS, BTFSC, BSC und BSS existieren.

Nun ja, auf Maschinen mit einem RAM, der im Prinzip bis zu 4 GB groß 
werden könnte, sind Befehle auf Einzelbits bzw. Boolean-Typen mit 
tatsächlich nur einem Bit eher etwas Ungewöhnliches.



Jörg W. schrieb:
> Du solltest dein C-Wissen mal aktualisieren. <stdbool.h> gibt's seit
> mehr als 20 Jahren, auch den Typ _Bool...

Das alles kenne ich, aber es sind alles nur Header und am Schluß ist es 
ein char, der 0 oder nicht 0 sein darf. Du hast ja explizit stdbool.h 
angeführt, also wache du mal lieber auf und nimm die Tomaten von den 
Augen.

Wir hatten die Datentypen-Diskussion ja vor Zeiten schon mal. Damals 
ging es allerdings mehr um die Integer-Typen, wo jemand behauptete, daß 
mit der passenden Header-Datei auch sowas wie uint16_t und Konsorten 
existieren. In Wahrheit ist das alles nur ein Thema der Textersetzung, 
sprich des Preprozessors und hat mit C nix wirklich zu tun. Wenn ich 
Lust drauf hätte, könnte ich auch den Datentyp "ottokar" mittels eine 
weiteren .h kreieren. Aber wozu?

W.S.

von Walter T. (nicolas)


Lesenswert?

W.S. schrieb:
> Wenn ich
> Lust drauf hätte, könnte ich auch den Datentyp "ottokar" mittels eine
> weiteren .h kreieren.

Mir wird der Zusammenhang mit den Bitfeldern nicht klar.

von W.S. (Gast)


Lesenswert?

Walter T. schrieb:
> ich habe eine 16-Bit-Variable und ein 16 Bit breites Bitfeld.

Und da es prinzipiell nicht festgelegt ist, an welcher Stelle in deiner 
Variablen welches Bit zu stehen hat, kannst du auch nicht erwarten, daß 
du mit der Annahme, daß du dieses durch manuelles Auszählen erledigen 
kannst, einen Erfolg haben wirst.

Und da wir hier offenbar von einem "größeren" µC reden, wäre es wohl 
eher angebracht, auf Bitfelder zu verzichten und die ganze Sache anders 
zu erledigen.

Eigentlich ärgern mich immer Beiträge, wo jemand irgend eine Spezialecke 
von C ausnutzen will, weil sie auf dem Papier so schön aussieht - und 
dann wird darüber geklagt, daß man dem Compiler das Leben schwer macht 
oder einen unerwartet großen Haufen Maschinencode als Ergebnis kriegt, 
wo auf dem Papier doch alles so schön aussah.

W.S.

von Peter D. (peda)


Lesenswert?

Für mich ist jede zusätzliche Umkodierung auch eine zusätzliche 
Fehlerquelle, daher lasse ich alles in der Order, wie es anliegt.
Das macht es auch einfacher, die Signale in der Hardware und in der 
Software zu debuggen.
Ich kann dann auch leicht die Zuordnung an einer einzigen Stelle ändern, 
z.B. wenn ein Pin anderweitig benötigt wird. Der eigentliche Code muß 
dazu nicht angefaßt werden, sondern nur neu compiliert.

Hast Du mal -Os probiert, das sollte den kleinsten Code ergeben.

von Yalu X. (yalu) (Moderator)


Lesenswert?

W.S. schrieb:
> Jörg W. schrieb:
>> Du solltest dein C-Wissen mal aktualisieren. <stdbool.h> gibt's seit
>> mehr als 20 Jahren, auch den Typ _Bool...
>
> Das alles kenne ich,

Den Eindruck habe ich nicht.

> aber es sind alles nur Header und am Schluß ist es
> ein char,

Nein, am Schluss ist es – wie Jörg schon schrieb – ein _Bool.

> der 0 oder nicht 0 sein darf.

Nein, nur 0 oder 1.

> Du hast ja explizit stdbool.h angeführt, also wache du mal lieber auf
> und nimm die Tomaten von den Augen.

In stdbool.h wird (u.a.) bool als _Bool definiert, aber nicht – wie du
immer wieder fälschlicherweise behauptest – als char.

von udok (Gast)


Lesenswert?

Walter T. schrieb:
> Man sieht: Es wird jedes Bit einzeln geprüft und zugewiesen, und man
> sieht auch, dass der Bit-Offset immer gleich ist. Ich hätte erwartet,
> dass der Compiler daraus eine Maskierung und eine Zuweisung macht.
>
> Was hindert ihn daran?

Keine Ahnung.
Ich kann dir nur sagen, dass es mit auf dem PC mit
Microsoft cl als auch mit clang-cl einwandfrei funktioniert.
Der gcc mag das aber nicht.

Ich habe nur mit 2 Bits getestet:
1
#include <stdio.h>
2
#include <string.h>
3
4
struct Inputs_s {
5
        unsigned int mpgKey :1;         /**< Funktionstaste am Handrad gedrueckt */
6
        unsigned int dirRight :1;       /**< Richtungswahlschalter rechts */
7
        unsigned int dirLeft :1;        /**< Richtungswahlschalter links */
8
        unsigned int locknutswitch :1;  /**< Schalter-Status Schlossmutterschalter */
9
        unsigned int eStopSignal :1;    /**< Not-Halt-Signal */
10
        unsigned int key2 :1;           /**< Taster 2 an Geraet */
11
        unsigned int key1 :1;           /**< Taster 1 an Geraet */
12
        unsigned int key0 :1;           /**< Drehgeber-Taster */
13
        unsigned int reserved : 6;      /**< Unbelegt */
14
        unsigned int driveStatusOk :1;  /**< Status-Signal Schrittmotor-Endstufe */
15
        unsigned int chargePumpOk :1;   /**< Ladungspum */
16
};
17
18
#define SWITCH_KEY0 0x80
19
#define SWITCH_KEY1 0x40
20
21
struct Inputs_s input_normalize(unsigned input)
22
{
23
    struct Inputs_s Out;
24
25
26
    memset(&Out, 0, sizeof(Out));
27
28
    Out.key0 =  !!(input & SWITCH_KEY0);
29
    Out.key1 =  !!(input & SWITCH_KEY1);
30
31
    return Out;
32
}

Assembler dump (ecx = Eingangsvariable, eax = Return value):
1
0000000000000000 <input_normalize>:
2
   0:   89 c8                   mov    eax,ecx
3
   2:   25 c0 00 00 00          and    eax,0xc0
4
   7:   c3                      ret

von Walter T. (nicolas)


Lesenswert?

Yalu X. schrieb:
> Nein, nur 0 oder 1.

Und wenn die Initalisierung fehlt, auch irgendein anderer Wert. Fragt 
nicht, woher ich das weiß.

udok schrieb:
> memset(&Out, 0, sizeof(Out));

Das ist ein anderes Spiel. Das ist ja wirklich implementationsabhängig.

: Bearbeitet durch User
von udok (Gast)


Lesenswert?

Ok, habs.

Der gcc will sowas sehen:
1
struct Inputs_s input_normalize(unsigned input)
2
{
3
    struct Inputs_s Out;
4
    
5
    *(unsigned*)(&Out) = 0;
6
    //memset(&Out, 0, sizeof(Out));
7
8
    Out.key0 =  (input & SWITCH_KEY0) ? 1 : 0;
9
    Out.key1 =  (input & SWITCH_KEY1) ? 1 : 0;
10
11
    return Out;
12
}

Das liefert dann:
1
0000000000000000 <input_normalize>:
2
   0:   89 c8                   mov    eax,ecx
3
   2:   25 c0 00 00 00          and    eax,0xc0
4
   7:   c3                      ret
5
   8:   90                      nop
6
   9:   90                      nop
7
   a:   90                      nop
8
   b:   90                      nop
9
   c:   90                      nop
10
   d:   90                      nop
11
   e:   90                      nop
12
   f:   90                      nop

von udok (Gast)


Lesenswert?

Walter T. schrieb:
> udok schrieb:
>> memset(&Out, 0, sizeof(Out));
>
> Das ist ein anderes Spiel. Das ist ja wirklich implementationsabhängig.

Du kannst die Variable natürlich anders initialisieren...
Warum soll das aber implementierungsabhängig sein?

von Walter T. (nicolas)


Lesenswert?

http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf

A b it-field  is  interpreted  as  a  signed  or  unsigned  integer 
type  consisting  of  the  specified
number  of  bits.107) If  the  value  0  or  1  is  stored  into  a 
nonzero-width  bit-field  of  type
_Bool, t he value of the bit-field shall compare equal to the value 
stored.

Hmm... das ist der Standard strenger als wir. Offensichtlich muss doch 
der Programmierer sicherstellen, dass in einem bool-Feld nur 0 oder 1 
gespeichert wird.





udok schrieb:
> Warum soll das aber implementierungsabhängig sein?

  The order of allocation of bit-fields within a unit (high-order to
low-order  or  low-order  to  high-order)  is  implementation-defined. 
The  alignment  of  the addressable storage unit is unspecified

Mache ich das Feld "reserved" zu einem anonymen Feld (unnamed field, das 
kannte ich bis jetzt auch noch nicht), wird es komplett wild und die 
Codegröße verdoppelt sich noch einmal.

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

W.S. schrieb:
> Rolf M. schrieb:
>> Hrmpf, warum musst du nur immer so einen Unsinn über C schreiben. Ein
>> boolescher Typ existiert in Standard-C seit 22 Jahren.
>
> Das ist allerdings ein verkappter char und kein echter und
> eigenständiger Datentyp.

Nein, das ist ein eigener Datentyp. Wenn ich bool b = 3 schreibe, steht 
in b nachher keine 3 drin, da das kein gültiger Wert für bool ist.

> Nun ja, auf Maschinen mit einem RAM, der im Prinzip bis zu 4 GB groß
> werden könnte, sind Befehle auf Einzelbits bzw. Boolean-Typen mit
> tatsächlich nur einem Bit eher etwas Ungewöhnliches.

Nun sind aber ARMe mit nur wenigen kB RAM auch nichts ungewöhnliches.

> Das alles kenne ich, aber es sind alles nur Header und am Schluß ist es
> ein char, der 0 oder nicht 0 sein darf. Du hast ja explizit stdbool.h
> angeführt, also wache du mal lieber auf und nimm die Tomaten von den
> Augen.

Davon stimmt auch nur die Hälfte. Ja, bool ist in einem 
(Standard!)-Header definiert, aber mit char hat das nichts zu tun. Der 
relevante Teil des zu meinem Compiler gehörenden stdbool.h:
1
#define bool    _Bool
2
#define true    1
3
#define false   0
Und C schreibt vor, dass das so definiert sein muss. _Bool ist ein 
eigenständiger Datentyp. Der Umweg über den Header wird nur gemacht, 
weil viele Programme vorher ihr eigenes bool definiert haben. Damit die 
nicht plötzlich alle ein Problem haben, weil bool jetzt ein 
Schlüsselwort ist, wurde es stattdessen _Bool genannt und nur über einen 
Header auf bool gemappt. So kann man bei einem vor-C99-Programm wählen, 
ob man bool haben will oder nicht.

> Wir hatten die Datentypen-Diskussion ja vor Zeiten schon mal. Damals
> ging es allerdings mehr um die Integer-Typen, wo jemand behauptete, daß
> mit der passenden Header-Datei auch sowas wie uint16_t und Konsorten
> existieren.

Das ist nicht nur eine Behauptung, sondern Fakt. Die "passenden 
Header-Dateien" sind Teil des C-Standards. Jeder C99-konforme Compiler 
muss die in korrekter Form mitbringen, sonst verstößt er gegen den 
C-Standard. Kann man in dem oben schon verlinkten Dokument 
http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf in Kapitel 
7.18 "Integer types <stdint.h>" nachlesen.

> In Wahrheit ist das alles nur ein Thema der Textersetzung,
> sprich des Preprozessors und hat mit C nix wirklich zu tun.

Natürlich hat die C-Standardbibliothek was mit C zu tun. uint16_t ist 
Teil davon, genau wie z.B. printf() oder malloc().

> Wenn ich Lust drauf hätte, könnte ich auch den Datentyp "ottokar" mittels
> eine weiteren .h kreieren. Aber wozu?

Weiß ich nicht. Aber ich weiß auch nicht, was dein selbst definierter 
Header mit der Standardbibliothek zu tun haben soll.

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

W.S. schrieb:
> Das alles kenne ich, aber es sind alles nur Header und am Schluß ist es
> ein char, der 0 oder nicht 0 sein darf.

Nein, ist es nicht. Im Gegensatz zu einem char, uint8_t etc. kümmert 
sich der Compiler nämlich drum, dass es nur die Werte 0 und 1 annehmen 
kann – auch wenn du 42 drauf zuweist.
1
$ cat foo.c
2
_Bool foo = 42;
3
$ cc -Os -S foo.c
4
$ cat foo.s
5
        .text
6
        .file   "foo.c"
7
        .type   foo,@object             # @foo
8
        .data
9
        .globl  foo
10
foo:
11
        .byte   1                       # 0x1
12
        .size   foo, 1
13
14
        .ident  "FreeBSD clang version 10.0.1 (git@github.com:llvm/llvm-project.git llvmorg-10.0.1-0-gef32c611aa2)"
15
        .section        ".note.GNU-stack","",@progbits
16
        .addrsig

> Du hast ja explizit stdbool.h
> angeführt, also wache du mal lieber auf und nimm die Tomaten von den
> Augen.

_Bool existiert auch ohne diesen Header, der Header kümmert sich nur 
drum, bool, true und false noch mit einzubringen (die vor C99 / ohne 
<stdbool.h> im application namespace liegen).

Sorry, du solltest vielleicht Standards auch mal lesen, bevor du dich 
über sie äußerst.

von bitfield (Gast)


Lesenswert?

udok schrieb:
> Ok, habs.
>
> Der gcc will sowas sehen:
>
1
> struct Inputs_s input_normalize(unsigned input)
2
> {
3
>     struct Inputs_s Out;
4
> 
5
>     *(unsigned*)(&Out) = 0;
6
> }
7
>


Das ist nicht erlaubt da es gegen die aliasing regeln verstößt. Man darf 
Objekte nur durch Pointer mit dem Typen des Objekten modifizieren 
(Ausnahme char).

Bitfields können für Register sehr praktisch sein, da der Compiler das 
shiften und maskieren automatisch übernimmt. Dadurch fallen mehrere 
potentiell Fehlerquellen weg (z.B. muss Wert vor Übergabe geschiftet 
werden oder tut das die Funktion?)

Und mit C++20 und constexpr bit_cast kann man auch endlich das Layout 
von Bitfields checken. Per #ifdef kann man dann auch die Reihenfolge 
umdrehen für big endian. Damit dürften nur noch esoterische Fälle 
überbleiben wo das nicht kompiliert.
1
#include <bit>
2
#include <cstdint>
3
4
struct bitfield {
5
    unsigned char a : 1;
6
    unsigned char b : 1;
7
    unsigned char c : 1;
8
    unsigned char d : 1;
9
    unsigned char e : 4;
10
    unsigned char f : 8;
11
12
    constexpr uint16_t asByte()
13
    {
14
        return std::bit_cast<uint16_t>(*this);
15
    }
16
};
17
18
static_assert(sizeof(bitfield) == 2);
19
static_assert(bitfield{}.asByte() == 0);
20
static_assert(bitfield{.a = 1}.asByte() == 1);
21
static_assert(bitfield{.e = 2}.asByte() == 2 << 4);
22
static_assert(bitfield{.f = 3}.asByte() == 3 << 8);

von Hannes (Gast)


Lesenswert?

Welcher C/C++ Standard? Ggf. ist die erwartete "Optimierung" hier 
illegal, da der Compiler keine writes erfinden darf. Deine Erwartung ist 
ja das alle Bits zusammen geschrieben werden. Das steht aber nicht im 
Code. Wenn z.b. Bit 0 geschrieben wird, darf (ggf) keines der anderen 
Bits angefasst werden. Das ist auch bei Vektortypen (SSE, AVX, NEON, 
SVE) so und hat dort schon zu lustigen Bugs geführt.

Aber sofern nur eine Vermutung von mir.

von MaWin (Gast)


Lesenswert?

Hannes schrieb:
> Das steht aber nicht im
> Code

as-if-Regel.

von Rolf M. (rmagnus)


Lesenswert?

bitfield schrieb:
> udok schrieb:
>> Ok, habs.
>>
>> Der gcc will sowas sehen:
>>> struct Inputs_s input_normalize(unsigned input)
>> {
>>     struct Inputs_s Out;
>>
>>     *(unsigned*)(&Out) = 0;
>> }
>>
>
> Das ist nicht erlaubt da es gegen die aliasing regeln verstößt. Man darf
> Objekte nur durch Pointer mit dem Typen des Objekten modifizieren
> (Ausnahme char).

Ein memcpy würde aber gehen.

von Hannes (Gast)


Lesenswert?

MaWin schrieb:
> Hannes schrieb:
>
>> Das steht aber nicht im
>> Code
>
> as-if-Regel.

Trifft hier nicht zu. Der Compiler darf keine Schreibzugriffe erfinden, 
auch wenn sie keine Daten im Speicher verändern, da sonst Locks nicht 
mehr funktionieren. MMn heißt das hier, die Bitzugriffe dürfen nicht 
zusammengefasst werden. Das ist etwas anderes als wenn z.B. bei mehreren 
aufeinander folgenden Schreibzugriffen auf Variablen, die ohne RMW 
auskommen, nur der letzte Schreibzugriff ausgeführt wird.

von (prx) A. K. (prx)


Lesenswert?

Bitfelder decken sich hinsichtlich Konsistenz nicht wirklich gut mit den 
Regeln für normale Daten, da Operationen auf einem bestimmten Bitfeld 
auch auf die Nachbarbits wirken. Die werden zwar nicht verändert, aber 
gelesen und später wieder zurückgeschrieben, ohne dass sich das im 
Quelltext niederschlägt.

Es könnte sich vielleicht lohnen, in der Standards und den Kommentaren 
dazu speziell danach zu graben.

: Bearbeitet durch User
von Walter T. (nicolas)


Lesenswert?

(prx) A. K. schrieb:
> Es könnte sich vielleicht lohnen, in der Standards und den Kommentaren
> dazu speziell danach zu graben.

Der Standard hält sich in Bezug auf Bitfelder extrem kurz. Es gibt 
keinen Anlaß dazu das so zu lesen, als ob sie irgendwie anders als 
normale Struct-Felder zu sehen seien. Lediglich beim Padding gibt es 
eine Sonderregelung.

Sagen die Kommentare etwas Anderes?

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


Lesenswert?

Walter T. schrieb:
> Der Standard hält sich in Bezug auf Bitfelder extrem kurz. Es gibt
> keinen Anlaß dazu das so zu lesen, als ob sie irgendwie anders als
> normale Struct-Felder zu sehen seien.

Das ist ja das Problem. Konsequent betrachtet dürfte es Bitfelder 
vielleicht überhaupt nicht geben. Denn ein Zugriff auf .driveStatusOk 
betrifft auch .chargePumpOk.

> Sagen die Kommentare etwas Anderes?

Eigentlich hatte ich vor, diesmal andere Leute suchen zu lassen. ;-)

: Bearbeitet durch User
von udok (Gast)


Lesenswert?

Walter T. schrieb:
> Der Standard hält sich in Bezug auf Bitfelder extrem kurz. Es gibt
> keinen Anlaß dazu das so zu lesen, als ob sie irgendwie anders als
> normale Struct-Felder zu sehen seien. Lediglich beim Padding gibt es
> eine Sonderregelung.

Lies noch mal nach im Standard.  Da steht noch mehr...

>
> Sagen die Kommentare etwas Anderes?

Ich heute auch keine Lust, für dich zu suchen.

von udok (Gast)


Lesenswert?

(prx) A. K. schrieb:
> Bitfelder decken sich hinsichtlich Konsistenz nicht wirklich gut mit den
> Regeln für normale Daten, da Operationen auf einem bestimmten Bitfeld
> auch auf die Nachbarbits wirken. Die werden zwar nicht verändert, aber
> gelesen und später wieder zurückgeschrieben, ohne dass sich das im
> Quelltext niederschlägt.
>
> Es könnte sich vielleicht lohnen, in der Standards und den Kommentaren
> dazu speziell danach zu graben.

Wenn du Bitfelder als Teil eines normalen Structs im RAM verwendest,
dann funktioniert das eigentlich sehr gut.
Wenn hinter dem Struct HW Register liegen, dann kann es Probleme geben.
Wobei moderne Controller eh meist separate Read und Write Register
zum Lesen und Setzen einzelner Bits haben.
Die speziellen Bitbefehle, werden von Compilern meist ignoriert.

von (prx) A. K. (prx)


Lesenswert?

udok schrieb:
> Die speziellen Bitbefehle, werden von Compilern meist ignoriert.

Siehe oben, ganz am Anfang vom Thread. Überwiegend Bitfeldbefehle. 
Allerdings natürlich im Register, ist ja eine Load/Store Architektur.

Die Einzelbitbefehle der AVRs werden regelmässig verwendet.

> Wenn du Bitfelder als Teil eines normalen Structs im RAM verwendest,
> dann funktioniert das eigentlich sehr gut.

Zur Illustration, wo man dabei landen kann:
https://lwn.net/Articles/478657/

: Bearbeitet durch User
von Walter T. (nicolas)


Lesenswert?

(prx) A. K. schrieb:
> Konsequent betrachtet dürfte es Bitfelder
> vielleicht überhaupt nicht geben. Denn ein Zugriff auf .driveStatusOk
> betrifft auch .chargePumpOk.

Und das ist okay nach der as-if-Regel. Hier ist nichts volatile. Jedes 
Feld wird so beschrieben, wie es ausgelesen wird. Kein type-punning, 
kein Alias. Was der Compiler daraus macht, ist ja so weit richtig. Nur 
umständlich.

von W.S. (Gast)


Lesenswert?

(prx) A. K. schrieb:
> Es könnte sich vielleicht lohnen, in der Standards und den Kommentaren
> dazu speziell danach zu graben.

Nö, das lohnt sich mMn überhaupt nicht wirklich. Stattdessen lohnt es 
sich, seine Vorstellungen über die gewünschte Funktionalität anders zu 
formulieren.

Eigentlich benötigt man für eine Boolean-Variable nur ein einziges Bit - 
aber mir ist kein Compiler bekannt, der sowas tut. Stattdessen belegt 
der Compiler für eine boolean-Variable zumindest die kleinste 
Speichermenge, die auf der Zielmaschine adressierbar ist - und das ist 
heutzutage eigentlich immer das Byte, im C Sprachgebrauch char genannt. 
(über das Verquicken von Textzeichen mit Rechengrößen will ich hier 
nicht nochmal diskutieren) und 7 der 8 Bit des besagten Bytes sind quasi 
'Verschnitt', der salopp gesagt Abfall ist.

Von Microsoft kenne ich auch Dinge wie Longbool, wo auch nur true oder 
false gespeichert werden sollen, aber insgesamt 32 Bit damit verbraucht 
werden.

So - und nun will der TO die einzelnen Bits einer 16 Bit Variablen je 
nach 16 verschiedenen boolean-Variablen setzen. So wie ich das sehe, 
macht es der Compiler bereits so gut wie er das kann - aber dem TO 
reicht das noch immer nicht. Nach meiner Ansicht ist das Erwarten von 
noch kompakterem Maschinencode schlichtweg übertrieben.

Es ist wie ich bereits sagte: Man landet bei boolean zumindest auf einem 
Byte (bzw. char) und nicht von selbst bei nur einem Bit und da gestaltet 
sich das Einsortieren von boolean-Variablen bzw. Zuständen in die Bits 
einer numerischen Variablen eben entsprechen umständlich - weil man die 
einzelnen Bits nicht separat voeinander ansprechen kann.

Was auf dem Papier so schön einfach aussieht, ist realiter ein größeres 
Problem, als man durch bloßes Draufschauen auf das Geschriebene meinen 
mag.

Da helfen auch die Einlassungen von Jörg nix, denn irgendwelche Papiere 
irgendwelcher Gremien können an den technischen Gegebenheiten kaun etwas 
ändern. Also ist eher das Nachdenken über das, was man tun will nötig, 
als das nochmalige Lesen der Standards.

W.S.

von udok (Gast)


Lesenswert?

(prx) A. K. schrieb:
>> Wenn du Bitfelder als Teil eines normalen Structs im RAM verwendest,
>> dann funktioniert das eigentlich sehr gut.
>
> Zur Illustration, wo man dabei landen kann:
> https://lwn.net/Articles/478657/

Armes Schwein, der den Fehler suchen musste.

Das macht aber nicht nur der Compiler so, sondern auch die CPU.

Jedesmal, wenn du 1 Byte liest, werden in Wirklichkeit 64 Bytes
gelesen, genauso beim schreiben (Cacheline).

Das sieht man auch bei den memcmp und memcpy Tests, die ich gemacht 
habe.
Da ist es ziemlich egal, ob man 1 Byte oder 32 Bytes vergleicht.
Dauert alles gleich kurz.

"volatile" hilft dir da auch nicht, das ist nur für den Compiler da.
Da braucht es einen "lock" prefix für Multiprozessor Synchronisation.
Da wird dann eine HW Leitung gesetzt, damit der Wert im L3 Cache landet,
wo ihn auch die anderen Prozessoren sehen.
Wobei das auch nicht mehr ganz wahr ist, in aktuellen Prozessoren.

von udok (Gast)


Lesenswert?

(prx) A. K. schrieb:
>> Die speziellen Bitbefehle, werden von Compilern meist ignoriert.
>
> Siehe oben, ganz am Anfang vom Thread. Überwiegend Bitfeldbefehle.
> Allerdings natürlich im Register, ist ja eine Load/Store Architektur.

Bezog mich auf die Intel Bitbefehle, die sehe ich fast nie.
Macht aber wahrscheinlich auch keinen Sinn, weil die & und | Befehle
da genauso schnell sind.

Beitrag #6807988 wurde vom Autor gelöscht.
von (prx) A. K. (prx)


Lesenswert?

udok schrieb:
> Bezog mich auf die Intel Bitbefehle, die sehe ich fast nie.

Weil langsamer. BT m,i sind bei Intel 2 Microops, TEST m.i nur einer. 
Bei Intels Dekoderstruktur waren Befehle mit mehr als einem Microop seit 
dem Pentium Pro deutlich benachteiligt. BTC/BTR/BTS sind teils noch 
schlimmer dran.

Und weil seit jeher kaum jemand diese Befehle verwendete, werden sie von 
den Prozessorbauern auch nicht optimal umgesetzt. Weshalb sie auch heute 
keiner verwendet.

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

Hannes schrieb:
> MaWin schrieb:
>> Hannes schrieb:
>>
>>> Das steht aber nicht im
>>> Code
>>
>> as-if-Regel.
>
> Trifft hier nicht zu.

Doch, natürlich trifft die zu. Der Compiler darf laut as-if-Regel alles 
beliebig verändern, außer volatile-Zugriffen, File-I/O und Terminal-I/O. 
Alles, aber wirklich alles andere ist durch die as-if-Regel quasi "zum 
Abschuss freigegeben."
Hier mal in Standard-Sprech:

"The least requirements on a conforming implementation are:
— At sequence points, volatile objects are stable in the sense that 
previous accesses are complete and subsequent accesses have not yet 
occurred.
— At program termination, all data written into files shall be identical 
to the result that execution of the program according to the abstract 
semantics would have produced.
— The input and output dynamics of interactive devices shall take place 
as  specified in 7.19.3."

> Der Compiler darf keine Schreibzugriffe erfinden, auch wenn sie keine
> Daten im Speicher verändern, da sonst Locks nicht mehr funktionieren.

Es geht doch gar nicht um das Erfinden von Schreibzugriffen, sondern 
darum, welche zu entfernen. Statt für jedes Bit separat auf die Variable 
zuzugreifen, soll der Zugriff nur einmal erfolgen.

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.