Bitstatusabfrage Macro - ist das sicher?

OP #5411071
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
Gast #5411073
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.
Gast #5411089
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.
#5411092
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.
OP #5411937
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
#5411948
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?
OP #5411974
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.
#5411976
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
Gast #5412261
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...
Persönliche Seite #5412325
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
Gast #5412345
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.

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