Beide Wege sind möglich.
Im ersten Fall ist nur das bit_2 volatile. Im zweiten Fall sind es alle
Elemente des Structs volatile. So ist es zumindest bei normalen
"Variablen". Möglicherweise ist das bei Bitfeldern auch anders und es
ist immer das gesamte Byte/Wort wo ein oder mehrere Bits eines Wertes
drin sind volatile.
Da dein Bitfeld in summe aber < uint8_t ist, ist es vermutlich sogar
besser es komplett volatile zu machen, ansonsten kommt der Compiler
vielleicht noch auf die Idee für bit_1 und bit_3_4 ein eigenen Speicher
zu reservieren.
Ich hab's mal mit dem GCC ausprobiert, und das Ergebnis entspricht den
Erwartungen:
Im ersten Fall wird nur bit_2 als volatile behandelt, im zweiten Fall
alle drei Elemente.
Ich habe jetzt nicht nachgeschaut, ob der Standard dieses Verhalten
genau so vorschreibt, wüsste aber nicht, was dagegen sprechen sollte.
Timmo H. schrieb:> Da dein Bitfeld in summe aber < uint8_t ist, ist es vermutlich sogar> besser es komplett volatile zu machen, ansonsten kommt der Compiler> vielleicht noch auf die Idee für bit_1 und bit_3_4 ein eigenen Speicher> zu reservieren.
Zumindest beim GCC gibt es diesbezüglich keinen Grund zur Besorgnis: In
den beiden Beispielen belegt die Struktur jeweils nur ein einzelnes
Byte. Da Zugriffe auf Bitfelder wegen der Bit-Maskiereri und -Schieberei
länger dauern als gewöhnliche uint8_t-Zugriffe, ist es schon sinnvoll,
nur diejenigen Felder volatile zumachen, die dies wirklich erfordern.
Yalu X. schrieb:> Ich hab's mal mit dem GCC ausprobiert, und das Ergebnis entspricht den> Erwartungen:>> Im ersten Fall wird nur bit_2 als volatile behandelt, im zweiten Fall> alle drei Elemente.>> Ich habe jetzt nicht nachgeschaut, ob der Standard dieses Verhalten> genau so vorschreibt, wüsste aber nicht, was dagegen sprechen sollte.
Das würde aber nur auf Architekturen funktionieren, die wirklich auf
einzelne Bits zugreifen können. Wie sollte ansonsten etwa bit_1
beschrieben werden, ohne dass auf bit_2 zugegriffen wird?
Das hat vor kurzem sogar handfeste Probleme im Linux-Kernel gegeben,
weil gcc bei Bitfields Code generiert, der auch benachbarte Felder
beschreiben kann. Siehe http://lwn.net/Articles/478657/
Andreas B. schrieb:> Das würde aber nur auf Architekturen funktionieren, die wirklich auf> einzelne Bits zugreifen können. Wie sollte ansonsten etwa bit_1> beschrieben werden, ohne dass auf bit_2 zugegriffen wird?
Man muss sich natürlich dessen bewusst sein, dass Bitfeldzugriffe von
sich aus i.Allg. nicht atomar sind, d.h. man muss sie ggf. atomar machen
(bspw. durch das Sperren von Interrupts).
Bei dem von dir verlinkten Beispiel war es so, dass der Compiler Code
generiert hat, der zum Schreiben eines einzelnen Bits unnötig viele
Bits (nämlich 64) beeinflusst hat, obwohl wegen des "unsigned int" nur
32 zu erwarten gewesen wären (selbst 8 hätten schon gereicht und wären
auch möglich gewesen). Die Linux-Entwickler sind im konkreten Fall davon
ausgegangen, dass eine spezielle Zugriffssteuerung unnötig ist, was sich
als Problem herausgestellt hat.
Dieses problematische Verhalten konnte ich bei dem Test mit dem AVR-GCC
aber nicht feststellen. Selbst wenn die Bitfelder als "int" oder
"unsigned int" deklariert werden, werden immer nur diejenigen Bytes
angetastet, die das betreffende Bitfeld auch tatsächlich enthalten.
Yalu X. schrieb:> Man muss sich natürlich dessen bewusst sein, dass Bitfeldzugriffe von> sich aus i.Allg. nicht atomar sind
Und eben das macht Bitfields, in denen ein volatile Element mit anderen
(ob volatile oder nicht) im kleinsten adressierbaren Datenwort (generell
Byte) kombiniert wird, prinzipiell unmöglich korrekt implementierbar.
Andreas B. schrieb:> Und eben das macht Bitfields, in denen ein volatile Element mit anderen> (ob volatile oder nicht) im kleinsten adressierbaren Datenwort (generell> Byte) kombiniert wird, prinzipiell unmöglich korrekt implementierbar.
Definiere "korrekt".
Auch ein 16-Bit-Integer auf einer 8-Bit-CPU erlaubt keinen atomaren
Zugriff. Bei der Programmierung mit Interrupts und Threads muss dies
beachtet werden, sonst wird evtl. Datenmüll produziert. Das ist aber
dann kein Fehler des Compilers, der Hardware oder des C-Standards,
sondern ganz alleine des Programmierers.
Ähnlich verhält es sich mit den Bitfeldern.
Yalu X. schrieb:> Bei dem von dir verlinkten Beispiel war es so, dass der Compiler Code> generiert hat, der zum Schreiben eines einzelnen Bits unnötig viele> Bits (nämlich 64) beeinflusst hat, obwohl wegen des "unsigned int" nur> 32 zu erwarten gewesen wären (selbst 8 hätten schon gereicht und wären> auch möglich gewesen). Die Linux-Entwickler sind im konkreten Fall davon> ausgegangen, dass eine spezielle Zugriffssteuerung unnötig ist, was sich> als Problem herausgestellt hat.>> Dieses problematische Verhalten konnte ich bei dem Test mit dem AVR-GCC> aber nicht feststellen. Selbst wenn die Bitfelder als "int" oder> "unsigned int" deklariert werden, werden immer nur diejenigen Bytes> angetastet, die das betreffende Bitfeld auch tatsächlich enthalten.
Da gabs vor kuzen einen lääämglichen Thread zu in den MLs,
siehe "Memory corruption due to word sharing":
http://gcc.gnu.org/ml/gcc/2012-02/threads.html#00005
Da ist auch Beispielcode anbei und Diskussion zwischen den Linuxern und
GCClern und C-Standard rauf und runter ;-)
Niedlicher Thread, auf den du da verlinkst. ;-)
Es gibt eben Dinge zwischen Himmel und Erde, die sich in C nicht sauber
lösen lassen. Wenn ein Zugriff auf volatile Variablen nur dort zulässig
ist, wo er im Quelltext drinsteht, dann sind volatile Bitfelder
grundsätzlich nur zulässig, wenn man die Zugriff darauf sauber trennen
kann. Damit aber sind sie so gut wie sinnlos.
Denn in einem Byte aus 2 4-Bit Feldern wie in
struct S { unsigned a:4, b:4; }
mit einer Verwendung in Form von
volatile S x;
ist bei auf mindestens Bytes operierender Hardware unvermeidlich, dass
ein Zugriff auf a auch einen Zugriff auf b durchführt, was aber im
Quelltext nicht drinsteht. Das ist zwar in diesem Fall ein eher
theoretisches Problem als ein praktisches, aber unlösbar.
Bliebe eigentlich nur die Möglichkeit, bei volatilen gepackten Daten
freundlich darauf hinzuweisen, dass man sich in Teufels Küche begibt.
Auch seitens des Compilers.
A. K. schrieb:> Niedlicher Thread, auf den du da verlinkst. ;-)
Jo, beim Lesen dachte ich mir auch "Popcorn!!!" :-)
> Denn in einem Byte aus 2 4-Bit Feldern wie in> struct S { unsigned a:4, b:4; }> mit einer Verwendung in Form von> volatile S x;> ist bei auf mindestens Bytes operierender Hardware unvermeidlich, dass> ein Zugriff auf a auch einen Zugriff auf b durchführt, was aber im> Quelltext nicht drinsteht. Das ist zwar in diesem Fall ein eher> theoretisches Problem als ein praktisches, aber unlösbar.
Der C-Standard hat das dadurch "gelöst", indem er volatile
"implementation definded" macht (C90 6.5.3, C99 6.7.3).
Neben den Unterschieden zwischen C und C++, siehe
http://gcc.gnu.org/onlinedocs/gcc/C_002b_002b-Volatiles.html#C_002b_002b-Volatiles
gibt's dann noch weitere, hausbackene Verwendungen von volatile wie "asm
volatile", volatile Funktionszeiger (noreturn-Semantik) und "register
volatile asm" (wird genauso behandelt wie nich-volatiles "register
asm").
Und dann gibt's auch noch -fstrict-volatile-bitfields
http://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html#index-fstrict_002dvolatile_002dbitfields-2305
Johann L. schrieb:> Und dann gibt's auch noch -fstrict-volatile-bitfields
Das scheint mir die Lösung für dieses Problem zu sein:
Andreas B. schrieb:> Das hat vor kurzem sogar handfeste Probleme im Linux-Kernel gegeben,> weil gcc bei Bitfields Code generiert, der auch benachbarte Felder> beschreiben kann. Siehe http://lwn.net/Articles/478657/Johann L. schrieb:> Da gabs vor kuzen einen lääämglichen Thread zu in den MLs,> siehe "Memory corruption due to word sharing":>> http://gcc.gnu.org/ml/gcc/2012-02/threads.html#00005
Die Option scheint im November 2011 (GCC 4.6) eingeführt worden zu sein,
die verlinkten Diskussionen fanden im Februar 2012 statt. Waren die
Leute im Februar einfach noch nicht auf dem neuesten GCC-Stand, haben
sie die neue Option noch nicht gekannt oder wirft die Option neue
Probleme auf, so dass man sie doch nicht als Lösung ansehen kann?
Yalu X. schrieb:> Die Option scheint im November 2011 (GCC 4.6) eingeführt worden zu sein,> die verlinkten Diskussionen fanden im Februar 2012 statt. Waren die> Leute im Februar einfach noch nicht auf dem neuesten GCC-Stand, haben> sie die neue Option noch nicht gekannt oder wirft die Option neue> Probleme auf, so dass man sie doch nicht als Lösung ansehen kann?
Linux muss auch ältere Compiler unterstützen, wenn es sich nur mit dem
aktuellsten gcc frisch vom GNU ftp-Server übersetzen ließe, gäbe es sehr
viele sehr unglückliche Leute… Zudem neue Versionen ja auch neue Bugs
einführen können.