Forum: PC-Programmierung Bitstatusabfrage Macro - ist das sicher?


von Bernd K. (bernd_k97)


Lesenswert?

Hallo Leute,
ich verwende seit langem dieses C-Makro zur Statusabfrage eines 
einzelnen Pins ohne 100%-ig sicher zu sein ob das sauber ist:

#define _ENC_S ((PINB>>_ENCPin)&1)

üblich ist ja eher:

#define bit_is_set(var, bit) ((var) & (1 << (bit)))

Ist der einzige Unterschied, dass letzteres auch mit einer Maske aus 
mehreren bits funktioniert, während das oben nur eine 
Einzelbit-Funktionalität hat?

Danke euch, Bernd

von Nop (Gast)


Lesenswert?

Bernd K. schrieb:

> #define bit_is_set(var, bit) ((var) & (1 << (bit)))
>
> Ist der einzige Unterschied, dass letzteres auch mit einer Maske aus
> mehreren bits funktioniert

Tut es nicht. Hauptvorteil ist aber, daß bei fester Bitnummer das 
Shiften zur Compilezeit aufgelöst werden kann.

von (prx) A. K. (prx)


Lesenswert?

Wobei es möglich ist, dass ein Compiler die erste Variante auch 
optimiert, wenn er darf, aber bei der zweiten ist das einigermassen 
sicher. Bei abgeschalteter Optimierung könnte die erste Variante ein 
Griff ins Klo sein.

von Nop (Gast)


Lesenswert?

A. K. schrieb:
> Wobei es möglich ist, dass ein Compiler die erste Variante auch
> optimiert, wenn er darf

Direkt optimieren kann er das nicht, weil die Variable da geshiftet 
wird, und die speist sich letztlich ja aus memory mapped IO mit 
volatile. Er könnte höchstens SEHR schlau sein und Variante 1 selber auf 
Variante 2 umschreiben, aber darauf würde ich mich nicht verlassen.

von (prx) A. K. (prx)


Lesenswert?

Nop schrieb:
> Er könnte höchstens SEHR schlau sein und Variante 1 selber auf
> Variante 2 umschreiben, aber darauf würde ich mich nicht verlassen.

GCC/amd64 macht das bei eingeschalteter Optimierung. Aber auch ich würde 
das lieber bleiben lassen. Bei GCC/AVR könnte das bei abgeschalteter 
Optimierung auf ziemlich schaurigen Code rauslaufen.

Das Prinzip ((x >> n) & 1) hat noch dazu den Nachteil, dass ein 
Rechtsshift recht wahrscheinlich eine Optimierung auf Operationen 
unterhalb sizeof(int) verhindert, zumindest wenn x nicht einfach bloss 
ein Port oder eine Variable ist, da Bits vom auf int erweiterten 
Zwischenergebnis nach unten rutschen.

: Bearbeitet durch User
von Bernd K. (bernd_k97)


Lesenswert?

Vielen Dank für die Hinweise. Meine Befürchtung war, dass
#define _ENC_S ((PINB>>_ENCPin)&1)  den Inhalt des Registers (hier PINB) 
unter bestimmten Umständen selbst schiebt und damit verändert. Das wäre 
natürlich tötlich. Ich hab das Verhalten noch nicht beobachtet und ein 
Zuweisungsoperator ist ja auch nicht vorhanden. Naja es blieben halt 
Zweifel.

Gruss, Bernd

von Dr. Sommer (Gast)


Lesenswert?

Bernd K. schrieb:
> #define _ENC_S ((PINB>>_ENCPin)&1)

Bezeichner die mir Unterstrich + Großbuchstaben oder 2 Unterstrichen 
anfangen sind in C und C++ der Standard Bibliothek vorenthalten und im 
User Code verboten.

von Wilhelm M. (wimalopaan)


Lesenswert?

Bernd K. schrieb:
> Vielen Dank für die Hinweise. Meine Befürchtung war, dass
> #define _ENC_S ((PINB>>_ENCPin)&1)  den Inhalt des Registers (hier PINB)
> unter bestimmten Umständen selbst schiebt und damit verändert. Das wäre
> natürlich tötlich. Ich hab das Verhalten noch nicht beobachtet und ein
> Zuweisungsoperator ist ja auch nicht vorhanden. Naja es blieben halt
> Zweifel.

Warum bleiben Zweifel?

von Bernd K. (bernd_k97)


Lesenswert?

Dr. Sommer schrieb:
> Bernd K. schrieb:
>> #define _ENC_S ((PINB>>_ENCPin)&1)
>
> Bezeichner die mir Unterstrich + Großbuchstaben oder 2 Unterstrichen
> anfangen sind in C und C++ der Standard Bibliothek vorenthalten und im
> User Code verboten.

Danke! Fand den einführenden Unterstrich sowieso immer lästig.

von Bernd K. (bernd_k97)


Lesenswert?

Wilhelm M. schrieb:
> Bernd K. schrieb:
>> Vielen Dank für die Hinweise. Meine Befürchtung war, dass
>> #define _ENC_S ((PINB>>_ENCPin)&1)  den Inhalt des Registers (hier PINB)
>> unter bestimmten Umständen selbst schiebt und damit verändert. Das wäre
>> natürlich tötlich. Ich hab das Verhalten noch nicht beobachtet und ein
>> Zuweisungsoperator ist ja auch nicht vorhanden. Naja es blieben halt
>> Zweifel.
>
> Warum bleiben Zweifel?

ob z.B. bestimmte Compiler-Optionen evtl. doch die Absicht des 
Programmierers missverstehen.

Kann man eigentlich das Ergebnis des Precompilers irgendwo anschauen. 
Also den Zustand des Quelltextes nachdem alle Makros und Konstanten in 
den Text eingesetzt sind? Die o-Files sind dann ja schon Maschinencode.

von Wilhelm M. (wimalopaan)


Lesenswert?

Bernd K. schrieb:
> Wilhelm M. schrieb:
>> Bernd K. schrieb:
>>> Vielen Dank für die Hinweise. Meine Befürchtung war, dass
>>> #define _ENC_S ((PINB>>_ENCPin)&1)  den Inhalt des Registers (hier PINB)
>>> unter bestimmten Umständen selbst schiebt und damit verändert. Das wäre
>>> natürlich tötlich. Ich hab das Verhalten noch nicht beobachtet und ein
>>> Zuweisungsoperator ist ja auch nicht vorhanden. Naja es blieben halt
>>> Zweifel.
>>
>> Warum bleiben Zweifel?
>
> ob z.B. bestimmte Compiler-Optionen evtl. doch die Absicht des
> Programmierers missverstehen.

Du solltest keine Optionen setzen, deren Konsequenz Du nicht verstehst. 
Aber: in diesem Fall ist das nicht möglich.

> Kann man eigentlich das Ergebnis des Precompilers irgendwo anschauen.
> Also den Zustand des Quelltextes nachdem alle Makros und Konstanten in
> den Text eingesetzt sind? Die o-Files sind dann ja schon Maschinencode.

Ja: Option -E

von Dr. Sommer (Gast)


Lesenswert?

Bernd K. schrieb:
> Kann man eigentlich das Ergebnis des Precompilers irgendwo anschauen

Eigentlich ist es hier sowieso unnötig den Präprozessor zu nutzen. Warum 
nicht einfach
1
inline int ENC_S () {
2
  return (PINB>>_ENCPin)&1;
3
}
Etwas mehr Tipparbeit, dafür weniger Ungewissheit dass da was 
durcheinander gehen könnte...

von Nop (Gast)


Lesenswert?

Dr. Sommer schrieb:

> }Etwas mehr Tipparbeit, dafür weniger Ungewissheit dass da was
> durcheinander gehen könnte...

Erstens besteht da kein Vorteil, und zweitens erhöht es die Ungewißheit, 
weil "inline" nur eine unverbindliche Anregung für den Compiler ist.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Bernd K. schrieb:
> Kann man eigentlich das Ergebnis des Precompilers irgendwo anschauen.

An einfachsten ist -save-temps zu den Optionen hinzuzufügen.  Dann wird 
für jede Compilation Unit ein .i (C), .ii (C++) sowie ein .s erzeugt 
bzw. nicht gelöscht.

Der präprozessierte Code hängt ab vom gewählten Debug-Format: Ab Dwarf-3 
können makros "debuggt" werden, daher enthält .i/.ii dann auch alle 
Makro-Definitionen, d.h. solche per #define, solche per -D sowie 
Built-in Makros.

A. K. schrieb:
> Nop schrieb:
>> Er könnte höchstens SEHR schlau sein und Variante 1 selber auf
>> Variante 2 umschreiben, aber darauf würde ich mich nicht verlassen.
>
> GCC/amd64 macht das bei eingeschalteter Optimierung. Aber auch ich würde
> das lieber bleiben lassen. Bei GCC/AVR könnte das bei abgeschalteter
> Optimierung auf ziemlich schaurigen Code rauslaufen.

Ohne Optimierung wird es in beiden Fällen nicht-optimalen Code geben.

Zudem werden Makros nicht optimiert :-) sondern es wwerden im Kontext 
der Ersetzung bestimmte Trandformationen gemacht.  Der Context wird hier 
vermutlich sowas sein wie
1
if (TEST(x)) ...
so dass z.B. für ein SBRC oder SBIC mehr optimiert werden muss als nur 
Shift + Bittest.  Andere denkbare Verwendungen wären
1
bool is_set = TEST(x);
oder
1
bool more_than_one_is_set = TEXT(x) + TEST(y) + TEST(z) > 1;

Falls es komplexe Ausdrücke werden, z.B. weil Makros geschachtelt 
werden, ist auch zu überlegen, stattdessen inline-Funktionen zu 
verwenden:
1
static inline __attribute__((__always_inline__))
2
bool is_set (uint8_t const volatile *port, uint8_t bitno)
3
{
4
    return (*port) & (1u << bitno);
5
}
6
7
uint8_t test (void)
8
{
9
    return is_set (&PINB, 7);
10
}

Allerdings ist auch bekannt, dass avr-gcc Arithmetik mit Bits wie in y = 
TEST(x) nicht optimal übersetzt.  Wer die sportliche Herausforderung 
liebt, dem kann ich gerne sagen, wo was geschraubt werden muss (nicht im 
avr-Teil).  Inzwischen ist v8 ja raus und die Entwicklung wieder in 
Stage I, d.h. offen.

https://gcc.gnu.org/gcc-8/changes.html#avr

von (prx) A. K. (prx)


Lesenswert?

Johann L. schrieb:
> Ohne Optimierung wird es in beiden Fällen nicht-optimalen Code geben.

:-)

Nur könnte ich mir vorstellen, dass der unoptimierte Code bei (x >> N) 
etwas brachialer ausfällt als bei (x & K).

: Bearbeitet durch User
von Dr. Sommer (Gast)


Lesenswert?

Nop schrieb:
> Erstens besteht da kein Vorteil, und zweitens erhöht es die Ungewißheit,
> weil "inline" nur eine unverbindliche Anregung für den Compiler ist.

Wenn man schlauer als der Compiler ist und sicher sein will dass es 
geinlined wird, fügt man ein "__attribute__((always_inline))" hinzu. 
Funktionen haben gegenüber Makros immer den Vorteil, dass es keine 
Probleme mit Scope und Kollisionen gibt, dass die Seiteneffekte von 
Parametern nicht mehrfach evaluiert werden, dass man sieht dass hier 
etwas aufgerufen wird und es sich nur um eine dumme Variable handelt, 
dass man nicht sehr mit Klammern aufpassen muss um keine Syntax-Fehler 
zu erzeugen. Daher würde ich sofern möglich immer Funktionen Makros 
bevorzugen.

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.