Hallo,
wie kann ich in C die 8 Bits eines Bytes mit Namen versehen und diese
dann gezielt mit dem Namen lesen, setzen, löschen?
uint8_t Status;
Status soll bestehen aus:
Platzhalter0 für bit0
Platzhalter1 für bit1
...
Platzhalter7 für bit7
Damit ich dann das Bit der Übersichtlichkeit mit Status.Platzhalter x
lesen, setzen, löschen kann?
Beispiele:
Status.Platzhalter1=0; // löscht Bit1 aus Byte Status
if (Status.Platzhalter0==1) { // Wenn Bit0 aus Byte Status = 0 dann...
Lg
Andreas
Mit __ attribute__(( _ packed_)) stehen deine Bits dann auch
compilerunabhängig ordentlich in Reihe da wo Du die vermutest, falls Du
mit memcpy auf dein struct zugreifen möchtest.
Aber aufgepaßt: der C-Standard definiert nicht, wie die benannten Bits
im Byte angeordnet werden. Das taugt also nicht für irgendeine Art von
externem Datentausch, auch nicht zum Interfacen von Registern, sondern
rein zur programm-internen Datenverarbeitung. Alles, was extern geht,
macht man daher über Bitmasken.
Der Hinweis auf "packed" ist falsch, denn der sorgt keineswegs für die
Anordnung von Bits, sondern dafür, daß bei structs/unions (das ist was
anderes als Bitfields) keine Pad-Bytes eingefügt werden.
Compilerunabhängig ist das schon deswegen nicht, weil es kein Teil des
C-Standards ist.
Alexander schrieb:> Wieso sind Bitfields kein Struct?
Weil die Anmerkung hier nach der Anordnung der einzelnen Bits war, nicht
nach eventuellem Padding zwischen den zugrundeliegenden Integern. Die
Anordnung der benannten Bits innerhalb der Integer ist nicht
Compiler-unabhängig, auch nicht mit packed.
Deswegen nutzt man Bitfields gerade dort nicht, wo sie verführerisch
aussehen: Register-Definitionen, Mapping vom Kommunikationsstrukturen /
Messages auf Bits und dergleichen.
Zu Packed siehe:
https://gcc.gnu.org/onlinedocs/gcc/Common-Type-Attributes.html#Common-Type-Attributes
Nein, meine Anmerkung bezog sich auf das Padding, deswegen auch __
attribute__(( _ packed_)). Endianness innerhalb eines "Bytes" hast Du
Dir selbst dazu gedichtet. Aber danke für den Hinweis.
P.S. In meinem Beispiel findest Du auch Bitfelder mit 3, 12 oder 14
Bits. Diese lassen sich schlecht in Bytes aufteilen. Deswegen nannte ich
sie "Bits" konnte schlecht "Bytes" dazu sagen.
Alexander schrieb:> Endianness innerhalb eines "Bytes" hast Du> Dir selbst dazu gedichtet.
Ist das so? Du schriebst:
> Mit __ attribute__(( packed)) stehen deine Bits dann auch> compilerunabhängig ordentlich in Reihe da wo Du die vermutest
Und dem ist eben nicht unbedingt so. Es geht auch nicht um Endianess
dabei, zumal die bei einem uint8_t wie im Ausgangsposting sowieso keine
Rolle spielt, sondern der Compiler darf die Bits einsortieren, wie er
lustig ist. Also bei uint8_t ist 01234567 genauso möglich wie 76543210.
Genauso erlaubt wäre sogar z.B. 05271634.
Manche Compiler haben Optionen, wie sie Bitfelder sortieren sollen, aber
das ist eben nicht compiler-unabhängig.
Das Padding bestimmt der Compiler. Dann, und nur dann stehen die "Bytes"
(Bits, Bitfelder, Integer, Bools, whatever) ordentlich in Reihe so dass
man mit memcpy auch auf die Structs zugreifen kann, wenn das Padding
eliminiert wurde. Ich kann Dir versichern dass mein Code ohne __
attribute__(( _ packed_)) nicht funktioniert hat.
Alexander schrieb:> Das Padding bestimmt der Compiler. Dann, und nur dann stehen die> "Bytes" (Bits, Bitfelder, Integer, Bools, whatever) ordentlich in Reihe
Sicher. Nur der Inhalt ist eben in seiner Reihenfolge nicht vom
C-Standard definiert. Deswegen nutzt man das nicht für Mapping auf
Register, Messages und dergleichen, wenn man's portabel halten möchte.
Daß Du mit "packed" eventuelle Lücken eliminiert hast, sagt nichts über
die Reihenfolge der Bits aus.
Deswegen habe ich die Reihenfolge der Bits die mich interessieren
_vorher_ als Bools im Struct festgelegt. Diese Reihenfolge stimmt sehr
wohl (mit __ attribute__(( _ packed_))). Warum sollte das nicht
portabel sein?
Alexander schrieb:> Deswegen habe ich die Reihenfolge der Bits die mich interessieren> vorher als Bools im Struct festgelegt.
Eben nicht. Die Reihenfolge, in der Du die Bits innerhalb eines
Basistyps (hier uint8_t) hinschreibst, haben keinen definierten Bezug
dazu, wo sie innerhalb des uint8_t dann landen. Siehe:
Beitrag "Re: Einzelbits in Byte Namen zuweisen und auswerten"
Das verwechselst Du mit structs ohne Bitfields, wo die Reihenfolge der
Deklaration dann mit der im Speicher übereinstimmt, minus padding (bzw.
plus packed).
Andreas schrieb:> wie kann ich in C die 8 Bits eines Bytes mit Namen versehen und diese> dann gezielt mit dem Namen lesen, setzen, löschen?
Im Prinzip garnicht. Es ist bei den meisten Architekturen immer eine
Folge von:
- Byte laden
- Byte verändern
- Byte zurückschreiben
erforderlich. Zumindest. Kann auch sein, daß es nur 16 Bit-weise oder
gar 32 Bit-weise geht - je nach Plattform. Eine Ausnahme sind z.B. die
PIC16, wo man mit einem Maschinenbefehl ein Bit in einem Byte abfragen
oder verändern kann. Auch die 8051 Architektur sieht so etwas für einige
Bereiche im RAM vor, aber so etwas hat keinen Eingang in C oder eine
andere maschinenunabhängige Programmiersprache gefunden. Damit bleibt
dir immer nur die Abfolge Laden/Verändern/Speichern des gesamten Bytes
übrig. Ganz egal, ob du das nun in irgend eine Verpackung tust oder
nicht. Für gewöhnliche Bytes im RAM geht das ja, aber bei Registern in
der Peripherie geht das oftmals nicht so, genaueres siehe Manual zum
Chip.
W.S.
W.S. schrieb:> Es ist bei den meisten Architekturen immer eine Folge von:> - Byte laden> - Byte verändern> - Byte zurückschreiben> erforderlich.
Was auf solchen Architekturen übrigens den Nebeneffekt hat, daß selbst
eine Zuweisung eines Bits nicht-atomar ist, was bei Betrachten des
Quelltextes aber nicht gleich auffällt, wenn das eine Zuweisung an ein
Bitfield ist.
Nop schrieb:> Das verwechselst Du mit structs ohne Bitfields, wo die Reihenfolge der> Deklaration dann mit der im Speicher übereinstimmt, minus padding (bzw.> plus packed).
Ich verwechsele hier gar nichts. Du redest von der Endianness eines
uint8_t. Ich rede von einem Bitfield Struct mit Booleans.
Wenn Dir die Endianness nicht vorher bekannt ist, dann musst Du es halt
doppelt definieren (Habe ich nie gebraucht):
1
typedef struct Status_t {
2
#if __BYTE_ORDER == __LITTLE_ENDIAN
3
bool Platzhalter0 : 1;
4
bool Platzhalter1 : 1;
5
bool Platzhalter2 : 1;
6
bool Platzhalter3 : 1;
7
bool Platzhalter4 : 1;
8
bool Platzhalter5 : 1;
9
bool Platzhalter6 : 1;
10
bool Platzhalter7 : 1;
11
#elif __BYTE_ORDER == __BIG_ENDIAN
12
bool Platzhalter7 : 1;
13
bool Platzhalter6 : 1;
14
bool Platzhalter5 : 1;
15
bool Platzhalter4 : 1;
16
bool Platzhalter3 : 1;
17
bool Platzhalter2 : 1;
18
bool Platzhalter1 : 1;
19
bool Platzhalter0 : 1;
20
#endif
21
} __attribute__((__packed__)) Status_t;
Willst Du mir jetzt sagen GNU ist kein Standard? Mit welchem Compiler
arbeitest Du? Funktioniert der Code bei Dir nicht?
Alexander schrieb:> Du redest von der Endianness eines uint8_t.
Nein, rede ich nicht, zumal ein uint8_t auch keine Endianess hat.
Endianess ist was völlig anderes, wie ich bereits mehrfach erwähnt habe.
Ich rede davon, daß das erste Bit in Deinem Bitfeld auf einem uint8_t
nicht auf Bit 0 des uint8_t liegen muß, und auch nicht auf Bit 7,
sondern auf irgendeinem Bit liegen kann. Das steht dem verwendeten
Compiler völlig frei (und hat mit Plattform-Endianess nichts zu tun),
auch wenn in der Praxis von unten oder von oben her aufgefüllt wird.
> Willst Du mir jetzt sagen GNU gcc ist kein Standard?
Standard ist allein der C-Standard, sonst gar nichts, und der sagt dazu
halt nichts.
> Funktioniert der Code bei Dir nicht?
Nicht portabel != funktioniert nicht. Völlig anderes Problem.
Nop schrieb:> Ich rede davon, daß das erste Bit in Deinem Bitfeld auf einem uint8_t> nicht auf Bit 0 des uint8_t liegen muß, und auch nicht auf Bit 7,> sondern auf irgendeinem Bit liegen kann.
Nennen wir es Bit-endianness. Praktisches Beispiel bitte. Reden wir hier
noch von Mikrocontrollern?
Achso, und "Bit-Endianess" ist es auch nicht, weil es mit der CPU
nichts, aber auch gar nichts zu tun hat. Es liegt einzig am Compiler,
wie er Bitfields implementiert.
Einfach mal hier schauen:
https://en.cppreference.com/w/c/language/bit_field
(unter Notes).
Daher für bspw. explizites Bit-Placement unbrauchbar bei etwa
µC-Registern.
Das heißt aber nicht, dass es nicht zufälligerweise funktionieren kann.
Ist aber eben IB und damit eben ein moving-target ;-)
Hinzu kommt wie oben schon gesagt wurde, dass meistens das als RMW
(nicht atomar) umgesetzt werden (muss), sofern der µC dafür keine
Unterstützung mitbringt (bit-set-Befehle wie etwa AVR SBI/CBI oder
set-reset-register)
Ich würde alle Zugriffe eher in (Inline)Funktionen kapseln.
Dann kann man den nichtatomaren
Aufruf gleich noch mit einbauen.
1
GetData1(structx)
2
SetData1(structx,data)
3
IsBusy(structx)
4
//Usw
Falls mann doch auf alle guten Ratschläge pfeift und die Bit Position
wichtig ist:
In jeder Übersetzungseinheiten per static_assert die structx Architektur
checken.
So habe ich es früher gemacht und tatsächlich bei einem Compilerwechsel
den gefährlichen Zustand entdeckt.
> typedef struct Status_t {> bool Platzhalter0 : 1;> ...> } __attribute__((_packed_)) Status_t;
Hier liegt das Problem doch beim "bool". Dessen Größe ist
implementation-defined (und hat sich beim GCC im Laufe der Zeit auch
verändert). Wenn der z.B. als "typedef { false, true } bool;" definiert
ist, liefert "sizeof Status_t" ohne packed 4, mit packed 1. Wo die
Bits dabei landen, weiß der Deibel.
Alexander schrieb:> Wo sollen die landen bei "mit packed 1" ;)
Mal ne ernste Frage: Wozu soll dieser Diskurs gut sein?
Ist es nur zwecks Besserwisserei oder steckt ein ernstes Anliegen
dahinter? In letzterem Falle wäre die Frage, ob es irgendwelche
Innereien eines Lowlevel-Treibers betreffen sollen oder ob es Signale
wie "Lampe_Ein" oder "Schotten_dicht" sein sollen, die in irgendwelchen
Algorithmen verwendet werden sollen. Sind es Treiber-Innereien, dann ist
das Arbeiten mit #define Einschaltbit (1<<7) oder so angesagt. Sind es
Signale wie genannt, dann ist angesagt, sowas in einzelne Funktionen zu
verpacken (a la void Lampe_Ein(void) oder so) und in eine separate
Quelle zu verfrachten. Dann ist die Firmware auch mit geringem Aufwand
portabel.
Und daß das Herumhampeln mit Bitfeldern eine heikle Sache ist, wurde dir
ja bereits gesagt.
W.S.
Warum sprichst du mich an? Ich habe nur einen Tipp gegeben.
Die Besserwisserei (geht's nicht immer darum?) dass die von mir
bevorzugte Lösung hypothetisch auf irgendeinem Mikrocontroller mal nicht
funktionieren könnte, weil nicht portabel kein C Standard, nicht
definiert, kein GNU compiler benutzt, bla bla habe nicht ich
losgetreten.
Ist doch alles auf gitlab. das 211_219_I_AEJ2003_X.dat file wird
ausgelesen und die Structs generiert. Ein Fahrzeug hat über 50
Steuergeräte. Jede CAN ID hat eine eigene Bitstruktur und daher sein
eigenes Struct. Der Aufbau ist immer gleich, daher problemlos via script
generierbar. Ich hab nur 3 CAN IDs gebraucht, und selbst das wäre mir zu
viel gewesen.
eingelesen wird mit
1
memcpy(&struct, msg, len);
Mir konnte immer noch keiner sagen auf welchem Mikrocontroller das nicht
funktionieren soll. Da hab ich ja richtig Glück gehabt dass es auf
meinem Intel und auf dem Kinetis läuft ;)
Alexander schrieb:> Da hab ich ja richtig Glück gehabt dass es auf> meinem Intel und auf dem Kinetis läuft ;)
Wenn Du bis jetzt nicht verstanden hast, was das Problem von
nicht-portablem Code ist, dann wirst Du es auch nicht mehr verstehen.
Ist halt mehr was für Profis.
Ich bin kein Programmierer und habe keine Anforderungen an MISRA. Und
Profi bin ich erst Recht nicht, ich habe noch nie was selbst
programmiert. Es macht keinen Sinn die Kontroverse aus Deinen Links hier
fortzuführen, wenn schon die Profis sich uneinig sind.
Alexander schrieb:> Ich bin kein Programmierer und habe keine Anforderungen an MISRA. Und> Profi bin ich erst Recht nicht, ich habe noch nie was selbst> programmiert.
Das sieht man. Allerdings machst Du dafür eine große Welle ...
> Es macht keinen Sinn die Kontroverse aus Deinen Links hier> fortzuführen, wenn schon die Profis sich uneinig sind.
Die Profis sind sich einig: Bit-Fields für Compiler/Architektur
übergreifenden Zugriff oder Zugriff auf µC-Register sind nicht-portabel,
weil IB. Man kann Glück haben - wie Du offensichtlich - doch es bleibt
wackelig (sofern Du das nicht durch Zusicherungen absicherst. Da Du aber
kein Programmierer bist, weißt Du auch nicht das Zusicherungen sind.
Auch wieder blöd ...)
Wir sind hier aber auch im Unterforum Mikrocontroller und Elektronik,
wäre nicht verkehrt anzunehmen es ginge um praktikable Ansätze im
Hobbybereich :)
Alexander schrieb:> Wir sind hier aber auch im Unterforum Mikrocontroller und Elektronik,> wäre nicht verkehrt anzunehmen es ginge um praktikable Ansätze im> Hobbybereich :)
Die praktikablen Ansätze wurden alle schon genannt.
Wenn Du Steuergeräte im Auto als µC bezeichnen willst, mit Ja (1)
Für die zweite Frage wäre es gut mal einen realen Fall aus der Praxis zu
zeigen, wo das ein tatsächliches Problem ist.
(Link reicht)
Alexander schrieb:> Wenn Du Steuergeräte im Auto als µC bezeichnen willst, mit Ja (1)
Bingo. Dann funktioniert der Datenaustausch eben nur mit viel Glück.
> Für die zweite Frage wäre es gut mal einen realen Fall aus der Praxis zu> zeigen, wo das ein tatsächlich ein Problem ist.
Hast Du das immer noch nicht verstanden?
Im Register des µC ist Bit0 für den Reset einer internen Peripherie
zuständig, und Bit1 ist ein Status-Bit.
Du hast zwar in dem Bit-Field die Bits nun von 0 bis 7 angeordnet, der
Compiler platziert aber das Element 0 nicht bei Bit0 sondern irgendwo
anders. Dann geht Dein vermeintlicher Reset der Peripherie ggf. nicht,
weil Du irgendein anderes Bit (evtl. das o.g. Status-Bit) erwischt. Und
mit der nä. Compiler-Version ist es wieder anders.
Wilhelm M. schrieb:> der Compiler platziert aber das Element 0 nicht bei Bit0 sondern> irgendwo anders.> [...] Und mit der nä. Compiler-Version ist es wieder anders.
Was gibt's daran nicht zu verstehen? Ich habe aber nach einem realen
Fall gefragt, wo einen das "Glück" verlässt. Welcher µC, welcher
Compiler (beachte: __ attribute__(( _ packed_)) = GNU Extension)??
Alexander schrieb:> Wilhelm M. schrieb:>> der Compiler platziert aber das Element 0 nicht bei Bit0 sondern>> irgendwo anders.>> [...] Und mit der nä. Compiler-Version ist es wieder anders.>> Was gibt's daran nicht zu verstehen?
Keine Ahnung ;-)
> Ich habe aber nach einem realen> Fall gefragt, wo einen das "Glück" verlässt. Welcher µC, welcher> Compiler (beachte: __ attribute__(( _ packed_)) = GNU Extension)??
Die Syntax _attribute_ ist GNU spezifisch. Du kannst aber mit C23 die
genormte Syntax verwenden:
1
[[attr]]
Damit bleibt es aber immer noch IB.
Außerdem hat "packed" NICHTS mit der Anordnung der Bits zu tun, das hast
Du immer noch nicht verstanden. Dazu hatte ich oben einen Link auf den
Standard gepostet wo das drin steht.
Wilhelm M. schrieb:> Außerdem hat "packed" NICHTS mit der Anordnung der Bits zu tun
Es korreliert aber! Und mir hat noch keiner den schwarzen Schwan
gebracht.
Im übrigen habe ich "Bytes" als "Bits" - im Sinne von Bitgruppe -
bezeichnet. Gemeint war, dass die Struct Member nur gepackt am richtigen
Offset zu finden sind. Und diese können aus nur 1 Bit bestehen. Das
ursprüngliche Post #4 war flapsige Umgangssprache und Nop hat das
erstmal korrigiert/thematisiert. Über die Anordnung der EINZELNEN Bits
INNERHALB eines Boolean (oder uint8_t Platzhalter0:1;) habe ich mir
vorher keine Gedanken gemacht... das hat "zufällig" über die Byte
Endianness funktioniert.
Alexander schrieb:> Wo sollen die landen bei "mit packed 1" ;)
Alexander schrieb:> Wilhelm M. schrieb:>> Außerdem hat "packed" NICHTS mit der Anordnung der Bits zu tun>> Es korreliert aber!
Jein. Natürlich hat das padding Einfluss. Aber davon sprechen wir hier
gar nicht, sondern einzig allein über die Bit-Anordnung im
underlying-type.
Aus der Bemerkung ziehe ich den Schluss, dass Du es immer noch nicht
verstanden hast, den Unterschied zwischen padding und Bit-Anordnung.
> Im übrigen habe ich "Bytes" als "Bits" - im Sinne von Bitgruppe -> bezeichnet. Gemeint war, dass die Struct Member nur gepackt am richtigen> Offset zu finden sind. Und diese können aus nur 1 Bit bestehen. Das> ursprüngliche Post #4 war flapsige Umgangssprache und Nop hat das> erstmal korrigiert/thematisiert. Über die Anordnung der EINZELNEN Bits> INNERHALB eines Boolean (oder uint8_t Platzhalter0:1;) habe ich mir> vorher keine Gedanken gemacht... das hat "zufällig" über die Byte> Endianness funktioniert.
Im übrigen ist uint8_t strenggenommen auch nicht zulässig als
underlying-type, sondern nur int, signed, unsigend und bool (s.a.
C-Standard).
Man kann das Ganze ungeheuer kompliziert betrachten.
Man kann aber auch einfach auf avr-gcc testen und dann die gültige
Bitorder anwenden:
1
To determine the compiler is avr-gcc, you need to test for 2 macros __GNUC__ and __AVR__
Die avr-gcc Entwickler sind recht konservativ, sie werden niemals die
ungünstige Vorbelegung von R0, R1 ändern. Daher werden sie erst recht
nicht die Bitorder ändern.
Peter D. schrieb:> Man kann das Ganze ungeheuer kompliziert betrachten.> Man kann aber auch einfach auf avr-gcc testen und dann die gültige> Bitorder anwenden:>
1
> To determine the compiler is avr-gcc, you need to test for 2 macros
2
> __GNUC__ and __AVR__
3
>
Das Thema static_assert o.ä. wurde doch auch schon längst genannt.
Das mit AVR hilft ihm aber auch nicht wirklich, weil er ja Intel und ARM
kommunizieren lässt ;-)
Klaus H. schrieb:> Danke.>> Hast du auch ein Beispiel in dem aus Optimierungszwecken die> Bitreihenfolge sich ändert?
Beispiel wegen Optimierung, also durch bspw. Umschalten von -O0 auf -O3
?: mir ist kein Beispiel bekannt.
Klaus H. schrieb:> Hast du auch ein Beispiel in dem aus Optimierungszwecken die> Bitreihenfolge sich ändert?
Ich häng' mich als Laie mal rein.
Ich könnte mir vorstellen, dass Bitmanipulationen, die man günstig mit
speziellen Befehlen für Halbbytes macht, eine solche Neuanordnung zur
Folge haben.