die 8 bit passen komplett in das uint8_t, also würde ich im ersten fall
eher sowas zu erwarten
0x000 |evt|evt|evt|evt|evt|evt|evt|dir|
bei dem union mit einem einzigen typ muss ich aber passen. sicher dass
da nicht noch ein uint8_t mit drin steckt?
dunno.. schrieb:> die 8 bit passen komplett in das uint8_t, also würde ich im ersten fall> eher sowas zu erwarten>> 0x000 |evt|evt|evt|evt|evt|evt|evt|dir|>>> bei dem union mit einem einzigen typ muss ich aber passen. sicher dass> da nicht noch ein uint8_t mit drin steckt?
sry, code war net vollständig. Es fehlt wirklich noch ein unit8_t, siehe
nachfolgend!
1
[c]
2
typedefunion{
3
4
struct{
5
uint8_tdir:1;
6
uint8_tevt:7;
7
}uint8_tByte;
8
9
}eventByte;
[/c]
Also sobald ich mein Bitfeld mit einer union "übermantle" setzt mir der
Compiler die Bitfelder in Byte richtig zusammen?
naja, du "übermantelst" das struct in der union mit einem uint8_t, und
kannst die union dann verwenden als wäre es ein einziges uint8_t (ist es
ja im speicher auch).. :)
fop schrieb:> Nein.>> Die Speicherbelegung könnte so aussehen :>
1
> Speicheraddrese Bit 7 6 5 4 3 2 1 0
2
> 0x000 |dir|evt|evt|evt|evt|evt|evt|evt|
3
> 0x001 | | | | | | | | |
4
>
> Das bleibt nämlich ein Struct. Die Union ist mit einem Mitglied wie bei> Dir nur zusätzlicher Schreibaufwand.
Ok mir ging es in erster Linie darum zu verstehen, ob durch das
zusammenlegen meiner struct-Elemente, Bit 0 von "evt" durch "dir"
überschrieben werden könnte.
Was ich im Code anstrebe könnte so aussehen:
1
typedefstructEVENT
2
{
3
union
4
{
5
struct
6
{
7
uint8_tdir:1;
8
uint8_tevtType:7;
9
10
}uint8_tEVENT1;
11
};
12
13
union
14
{
15
struct
16
{
17
uint8_tdir:1;
18
uint8_tevtType:7;
19
20
}uint8_tEVENT2;
21
};
22
23
}__attribute__((packed));
Macht es hierbei Sinn das Attribut packed zu verwenden? Könnte ich damit
die nachfolgende Speicherbelegung erreichen?
1
Speicheraddrese Bit 7 6 5 4 3 2 1 0
2
0x000 |dir|evt|evt|evt|evt|evt|evt|evt| -- EVENT1
3
0x001 |dir|evt|evt|evt|evt|evt|evt|evt| -- EVENT2
Ich versuche ein Konstrukt zu erschaffen, dass so wenig Speicher wie
möglich in Anspruch nimmt.
bist Du sicher, dass das geht?
Fehlt da vielleicht ein Semikolon vor uint8_t und das struct bleibt dann
unbenamt weil die Elemente eindeutig sind?
Robert M. schrieb:> Ich versuche ein Konstrukt zu erschaffen, dass so wenig Speicher wie> möglich in Anspruch nimmt.
Dann beschreib doch, wie es aussehen soll. Prinzipiell ist es möglich,
Arrays von 8 Bit anzulegen, die jeweils 1 bit dir und 7 bit event haben.
Ob Union, Structs oder Bitschieben für Dich und Deinen Prozessor das
beste ist, wird man sehen.
fop schrieb:> Aber Vorsicht : die genaue Anordnung im Speicher bleibt Sache des> Compilers.
Das möchte ich nochmal hervorheben, weil es unions fast nutzlos macht.
Beispiel 1:
Wenn man zum Beispiel Bitfelder mittels Union in eine uint8_t über UART
schickt und dann wieder auspackt, stimmt der Inhalt nicht zwingend, weil
der PC die Bits anders einreiht als der Controller.
So ist es mir ergangen.
Beispiel 2:
Man deklariert sich Bitfelder mit Unions für die I2C-Kommunikation mit
z.B. einem Temperatursensor. Der hat ja z.B. ein Statusregister mit
viele Einzelbits drin - praktisch: Man kann sich die Bits ohne Masken
und schieben aus dem Union ziehen. Klappt auch, und ist schnell.
Aber wehe man portiert den Code auf einen anderen Controller. Dann wird
es unlustig.
Daher Vorsicht mit unions.
Das ist sehr, sehr schade, denn sie könnten so nützlich sein!
Bitfields und unions haben Einschränkungen bzw. Freiheitsgrade, die
ihren naheliegenden Einsatz manchmal einfach unmöglich machen:
1) Die Anordnung der Bits in einem Bit-Field ist Compiler-abhängig.
http://en.cppreference.com/w/cpp/language/bit_field
Damit scheidet i.A. die ggf. naheliegende Verwendung von ihnen für
HW-Register oder Serialisierungen aus. Sie eignen sich daher tatsächlich
nur für abstrakte Bit-Mengen ohne HW-Bezug oder Plattform-Neutralität.
2) Die Verwendung von unions zum "type-punning" in C++ ist UB (in C aber
ok), denn man darf nur das "active member" verwenden (ist in C++ auch
logisch, da Elemente einer union eben auch UDT sein können) (es sei
denn, sie habe eine common-initial-sequence ...)
Dies ist dann bspw. problematisch, wenn man C-Quellen (unbedarft) mit
einem C++-Compiler verwendet (wobei im C-Code natürlich die
problematischen Fälle nicht vorkommen können, und in der Praxis für
fundamentale DT der C++-Compiler wohl so wie der C-Compiler arbeitet,
aber es ist nicht korrekt, und statische Code-Checker melden das)
Wilhelm M. schrieb:> Damit scheidet i.A. die ggf. naheliegende Verwendung von ihnen für> HW-Register oder Serialisierungen aus. Sie eignen sich daher tatsächlich> nur für abstrakte Bit-Mengen ohne HW-Bezug oder Plattform-Neutralität.
In den Headerfiles für PICs, die Microchip mit seinen Compilern
mitliefert, werden alle Registerbits in Bitfeldern angelegt. Die
Programmierer des XC8 und des XC16 (ist der GCC) sehen das also exakt
andersherum.
MfG Klaus
Klaus schrieb:> Wilhelm M. schrieb:>> Damit scheidet i.A. die ggf. naheliegende Verwendung von ihnen für>> HW-Register oder Serialisierungen aus. Sie eignen sich daher tatsächlich>> nur für abstrakte Bit-Mengen ohne HW-Bezug oder Plattform-Neutralität.>> In den Headerfiles für PICs, die Microchip mit seinen Compilern> mitliefert, werden alle Registerbits in Bitfeldern angelegt. Die> Programmierer des XC8 und des XC16 (ist der GCC) sehen das also exakt> andersherum.
Dann darf man die Headerfiles eben nur zusammen mit dem GCC mit diesem
ABI verwenden ... das steht wahrscheinlich im Kleingedruckten.
Wilhelm M. schrieb:> Dann darf man die Headerfiles eben nur zusammen mit dem GCC mit diesem> ABI verwenden
Oder man sollte mal fünf Minuten investieren und den eigenen Compiler
auf sein Verhalten hin untersuchen.
Ist ja keine Raketenwissenschaft, und dann hat man bis zum nächsten
Compilerwechsel Gewissheit.
Sind denn, von hypothetischen Beispielen abgesehen, real existierende
Compiler für eine Prozessorarchitektur bekannt, die Bitfelder
unterschiedlich anordnen?
Hat schon mal jemand so etwas gesehen oder ist das nur hypothetisches
"Ich kann aber den Standard lesen"-Getue aus der Informatik-Vorlesung?
Wilhelm M. schrieb:> Dann darf man die Headerfiles eben nur zusammen mit dem GCC mit diesem> ABI verwenden ..
Die Headerfiles für einen PIC machen ja auch nur bei Compiler für einen
PIC Sinn. Wer würde sie denn für einen anderen Prozessor sinnvoll
benutzen wollen, da gibts ja ganz andere SFRs. Und wieviele ABIs gibts
beim GCC für den gleichen Prozessor?
MfG Klaus
Klaus schrieb:> Wilhelm M. schrieb:>> Dann darf man die Headerfiles eben nur zusammen mit dem GCC mit diesem>> ABI verwenden ..>> Die Headerfiles für einen PIC machen ja auch nur bei Compiler für einen> PIC Sinn. Wer würde sie denn für einen anderen Prozessor sinnvoll> benutzen wollen, da gibts ja ganz andere SFRs. Und wieviele ABIs gibts> beim GCC für den gleichen Prozessor?
Es gibt aber mehr Compiler als die der GCC ...
Ein Nachteil mit diesen Bitfeldern ist daß der Adressoperator damit
nicht funktiouert, man kann keinen Pointer auf so einen Eintrag bekommen
(was ja auch logisch ist, leider konsequenterweise auch dann nicht wenn
es ganze 8 Bit sind an einer 8 bit Grenze)
Wilhelm M. schrieb:> Und wieviele ABIs gibts>> beim GCC für den gleichen Prozessor?
Die Frage muss lauten wie viele ABIs gibts zum Beispiel für ARM Cortex
(oder irgendeinen anderen Controller) die sich in diesem Punkt
unterscheiden (oder überhaupt unterscheiden). Das hat erstmal nichts mit
gcc oder irgendeinem anderen Compiler zu tun.
> Es gibt aber mehr Compiler als die der GCC ...
Das wäre egal wenn sich alle ans ABI der Zielplatform halten. Welche
Compiler halten sich nicht daran?
Bummsfallera schrieb:> fop schrieb:>> Aber Vorsicht : die genaue Anordnung im Speicher bleibt Sache des>> Compilers.>> Das möchte ich nochmal hervorheben, weil es unions fast nutzlos macht.
Nutzlos macht es sie nicht. Sie sind aber weder dafür geeignet, noch
dafür gedacht, um sie für Konvertierungen zu missbrauchen.
Wilhelm M. schrieb:> Bitfields und unions haben Einschränkungen bzw. Freiheitsgrade, die> ihren naheliegenden Einsatz manchmal einfach unmöglich machen:
Ich finde diese Art der Nutzung von unions nicht so naheliegend, weil
irgendwie umständlich. Statt einfach von a nach b zu casten, definiere
ich mir extra einen eigenen Typ, kopiere die Daten da rein und wieder
raus und habe damit kunstvoll ein verstecktes Äquivalent eines Casts
gebastelt.
Rufus Τ. F. schrieb:> Sind denn, von hypothetischen Beispielen abgesehen, real existierende> Compiler für eine Prozessorarchitektur bekannt, die Bitfelder> unterschiedlich anordnen?>> Hat schon mal jemand so etwas gesehen oder ist das nur hypothetisches> "Ich kann aber den Standard lesen"-Getue aus der Informatik-Vorlesung?
Die Frage kann man auch umdrehen:
Kann jemand beweisen, dass alle bisherigen und zukünftigen Compiler
Bitfelder genau gleich anordnen, oder ist das nur eine naive "ich geh
einfach davon aus, dass das schon passen wird"-Annahme von
Möchtegern-Programmieren?
Der Standard ist auch nicht dafür da, damit man ihn einfach ignoriert,
sondern damit man weiß, worauf man sich verlassen kann und worauf nicht.
Rufus Τ. F. schrieb:> Hat schon mal jemand so etwas gesehen oder ist das nur hypothetisches> "Ich kann aber den Standard lesen"-Getue aus der Informatik-Vorlesung?
Für die Microchip PIC gibts es diese Bitfelder in allen Headerfiles,
ebenfalls vom Microchip geliefert. Ich gehe mal davon aus, daß die
meisten wenn nicht alle Großkunden von Microchip diese Bitfelder auch so
benutzen. ADCON0bits.ADON = 1 ist auch viel lesbarer, als diese Zeilen
voller << XYZ, bei denen der Compiler noch nicht mal überprüfen kann, ob
XYZ in diesem Register überhaupt vorkommt. Sollte das in einer neueren
Version des Compiler mal nicht passend funktionieren, bricht es allen
alten Code, auch den der professionellen Großkunden. Damit würde sich
die Fa. Microchip direkt in ein schwarzes Loch katapultieren, sie werden
also darauf achten, daß das nicht passiert (auch beim XC16 aka GCC).
Ob das mit dem Standard überhaupt stimmt, kann ich nicht beurteilen. Ich
meine aber gelesen zu haben, daß es in neueren Standards bei Bitfeldern
eine Präzisierung gegeben hat.
MfG Klaus
Rolf M. schrieb:> Kann jemand beweisen, dass alle bisherigen und zukünftigen Compiler> Bitfelder genau gleich anordnen,
Das muss man nicht beweisen. Der Unterschied zwischen
"ich-sitz-hier-in-der-Informatik-Vorlesung" und realer Arbeit ist der,
daß man sich notfalls fünf Minuten mit seinem neuen Compiler
beschäftigt, wenn man ihn aus welchen Gründen auch immer gewechselt hat.
Wer mit einem Compiler ernsthaft arbeitet, kennt ihn, und untersucht
potentiell kritische Verhaltensweisen.
Wenn die Compiler für eine bestimmte Zielarchitektur sind, dann kann man
in der Tat davon ausgehen, denn es ist abseits des heiligen Standards
eine etablierte Arbeitsweise.
Wer Software "beweisen" will, der arbeitet nicht, sondern der geht
akademischen Spielereien nach.
Oh, und diese Kluft zwischen akademischen Anspruchsdenken und realer
Arbeitswelt ist auch daran beteiligt, daß auch in diesem Jahrzehnt nach
wie vor viel mit C89 gearbeitet wird, ohne die tollen neuen Dinge zu
nutzen, die die verschiedenen neuen Standards mit sich gebracht haben,
sei es C99 oder C11.
In der normalen Anwendungsentwicklung ist es völlig egal, wie der
Compiler die Bitfelder intern anlegt und in der hardwarenahen
Entwicklung ist die Anpassung der Strukturdefinitionen bei einem Wechsel
des Compilers im laufenden Projekt das geringste Problem (erlebtes
Beispiel: STM8S). Der Einsatz von 'struct' und 'union' ist in diesem
Bereich ein sehr elegantes und übersichtliches Stilmittel an dessen
Einsatz nur Möchtegern-Programmierer scheitern.
union jack schrieb:> In der normalen Anwendungsentwicklung ist es völlig egal, wie der> Compiler die Bitfelder intern anlegt
Nicht wenn mehrere Softwaren auf unterschiedlichen Platformen auf
irgendeinem Wege binäre Daten austauschen müssen oder auf irgendein von
höherer Stelle spezifiziertes Datenformat. Das ist nicht unüblich. In
dem Falle sollte man sich dreimal und viermal überlegen was man macht
und wie man es macht.
Notfalls (wenn man wirklich diesen Weg gehen will) kann man das
beabsichtigte Verhalten (und Abweichungen davon) auch per
(Compile-Time-)Assert dokumentieren (und eventuelles späteres
Rätselraten vermeiden), das dokumentiert dann auch daß man zumindest mal
drüber nachgedacht hat und sich des Sachverhaltes bewußt war.
Bernd K. schrieb:> Nicht wenn mehrere Softwaren auf unterschiedlichen Platformen auf> irgendeinem Wege binäre Daten austauschen
Seit wann tauschen verschiedene Plattformen ihre Hardwareregister?
Wilhelm M. schrieb:> Damit scheidet i.A. die ggf. naheliegende Verwendung von ihnen für> HW-Register ...
MfG Klaus
Man kann sich darüber streiten (eigentlich kann man es nicht) ob es
wirklich die Anwendungsschicht ist die sich über die Bitpositionen von
Binärdaten kümmern sollte, ich würde das eher etwas tiefer ansetzen. Die
entsprechenden Überlegungen muss man natürlich immer anstellen, auch bei
den Alternativen (gerade, wenn zB. 32-bit UDP-Daten verarbeitet werden
sollen). Ich halte es trotzdem für unerreicht (aus Sicht des
Quellcodes), zb. einen UDP-Header in eine union schreiben zu können und
anschliessend über ein struct direkten Zugriff auf alle Felder zu haben.
Und auch bei den Alternativen muss ich das beabsichtigte Verhalten
dokumentieren (weil ich mich dort auch auf eine Datenstruktur festlegen
muss) und der Arbeitsaufwand für spätere Änderungen ist in beiden Fällen
gegeben. Ich behaupte nicht, das der Weg über structs und unions aus der
Sicht des Binärcodes der effektivere ist, aber der GCC macht hier einen
sehr guten Job.
Klaus schrieb:> Seit wann tauschen verschiedene Plattformen ihre Hardwareregister?
Wer hat von Hardwarregistern gesprochen? Es ging um Unions und
Bitfelder.(wie im Thread-Titel) Meine Antwort bezog sich also auf
Unions, Structs und Bitfelder.
Bernd K. schrieb:> Klaus schrieb:>> Seit wann tauschen verschiedene Plattformen ihre Hardwareregister?>> Wer hat von Hardwarregistern gesprochen? Es ging um Unions und> Bitfelder.(wie im Thread-Titel) Meine Antwort bezog sich also auf> Unions, Structs und Bitfelder.
Der Punkt ist, dass man sie in Programmen intern benutzt, oder bei
Hardwareregistern (da dort Portabilität eh keine Rolle spielt). Für die
Kommunikation mehrerer Systeme untereinander sind sie wie schon gesagt
weder gedacht, noch geeignet. Wenn man sie trotzdem dafür benutzt, muss
man sich eben selbst um die Probleme kümmern, die daraus entstehen
können.
Rolf M. schrieb:> Wenn man sie trotzdem dafür benutzt, muss> man sich eben selbst um die Probleme kümmern, die daraus entstehen> können.
Man kann um Zeit zu sparen ein paar Asserts an zentraler Stelle
plazieren die das Compilerverhalten abfragen und damit die auf
unbestimmte Zukunft (irgendwann oder meistens nie) verschobene Arbeit
mit einem Alarmsystem auszurüsten das sofort unmißverständlich klingelt
wenn es nötig wird nochmal Hand anzulegen. Das ist mein Ansatz. Meist
(99%) wird nichts nötig und die Zeit ist gespart. So frage ich zum
Beispiel die Größe eines verdächtigen ("wackeligen") Structs mit einem
Compile-Time Assert ab um in der Zukunft unerwartet geänderte
Alignment-Regeln sofort zu bemerken. Oder auch die Größe von Enums
(damit ich sie gefahrlos in structs unterbringen kann).
Bitfelder benutze ich seit geraumer Zeit schon nicht mehr, konnte mich
eigentlich nie wirklich damit anfreunden, sie wirken auf mich wie ein
unbedacht hastig drangeflicktes, über-abstraktes aber unterdefiniertes
und dadurch leider vom Geist der restlichen Sprache und der darunter
liegenden Maschine entrücktes Feature, ich komme problemlos ohne sie
aus, ohne daß es unleserlicher oder aufwendiger würde.