Hallo,
eine Frage an die C-Profis:
Ich habe eine Union für die Uhrzeit gemacht.
typedef union
{
unsigned short long uns24;
unsigned char v[3];
struct
{
unsigned char sekunde;
unsigned char minute;
unsigned char stunde;
};
}S_M_H_Time;
In die wird ein RTCC reingelesen und das funktioniert soweit auch alles.
Jetzt will der Kunde Wochenprogramme haben, also muß aus dem RTCC auch
der Wochentag rausgelesen werden. Das dürfte auch das kleinere Problem
sein.
Die Instanz meiner Union ist Bestandteil eines großen Arrays, in dem ich
nichts mehr verschieben kann. Ein 4.Byte an die Union hängen, geht daher
nicht.
Nun hat der Tag max. 24h, das sind 5 Bit, weshalb 3 Bit beim Byte für
die Stunde immer frei bleiben. Die Idee wäre deshalb, diese für die 7
Wochentage zu nutzen.
Allerdings finde ich aus dem Stehgreif keine Syntax, mit der ich die
Union so erweitern kann, dass ich die oberen 3 Bit des obersten Bytes
isoliere. Ein direktes Ansprechen dieser 3 Bit würde aber den Code
durchaus etwas übersichtlicher gestalten.
Kann da jemand helfen?
Besten Dank im Voraus!
BL schrieb:> unsigned short long uns24;
short oder long.
Wenn es long ist, hast du schon 4 Byte. dann kannst du auch die strcut
erweitern.
Wenn es short ist, dann belegt die struct eigentlich 3 Byte.
Wegen des Alignements wird sie aber meist auf 4 BYte aufgeblasen, damit
sie ohne Problem als Array benutzt werden kann.
Teste das mal mit sizeof(S_M_H_Time)
kleiner Hinweis:
unions zur Typwandlung nutzen ist böse (bzw. kann böse sein ;) )
Aber ich glaube die erfahrung wo das dann richtig knallt muss jeder erst
mal selber machen. Und ja ich habe die unions früher auch für
Typwanlungen verwendet weil es soooo praktisch ist.
> sollte gehen
Und sind dann die 3 bits von "tag" die oberen oder die unteren 3 bits?
Ist v[0] dann gleich "sekunde" oder "stunde/tag"?
Das ist alles implementation defined, also abhängig vom benutzten
Compiler und Prozessor!
Herzlichen Dank erst mal für die schnellen Antworten!
>Teste das mal mit sizeof(S_M_H_Time)
Ergibt 3.
Der Compiler ist XC8, da habe ich dieses "short long" von irgendwoher
einfach abgeschrieben. Mit dem XC8 arbeite ich erst ein halbes Jahr.
Vorher hatte ich den CC8e.
> unsigned char stunde : 5;> unsigned char tag : 3;
Das haut hin, Danke nochmal!
Zur Typumwandlung - ich bin leider kein gelernter C-Experte.
Diese Union-Geschichte fand ich daher zur Typumwandlung eigentlich immer
ganz elegant. Früher hab ich das mit festen RAM-Addressen gemacht, das
hatt wirklich seine Tücken.
Wie würde man es denn richtig(er) machen?
unions in C sind ausschließlich zum Speicher sparen gemacht; erst einen
Eintrag schreiben und dann einen anderen lesen ist
implementation-defined behaviour, als gefährlich und unportabel. Siehe
Serialisierung. Die dort vorgestellte Library kann was du brauchst
in korrekt und plattformunabhängig.
BL schrieb:> Zur Typumwandlung - ich bin leider kein gelernter C-Experte.> Diese Union-Geschichte fand ich daher zur Typumwandlung eigentlich immer> ganz elegant.
Liest man leider sehr oft und ist bei
bei C: implementation-defined behaviour
bei C++: undefined behaviour
Immer richtig ist ein shift-N-mask-N-or.
> Früher hab ich das mit festen RAM-Addressen gemacht, das> hatt wirklich seine Tücken.
Das ist dann im Kern dasselbe wie bei den unions:
implementation-defined.
Das implementation-defined kann man natürlich in seinem Code
berücksichtigen, etwa in C durch bedingte Compilierung. Ist aber
trotzdem ein "wackelige" Lösung.
Bitfelder sind nicht portabel (eben weil implementation defined). Das
sind direkte Hardwarezugriffe (wg. ggf. unterschiedlicher Byte Order)
aber auch.
Hier sind Bitfelder m.E. sogar völlig ungefährlich.
Wenn ich das richtig verstehe, will der TO den Wochentag sowieso immer
über den Union-Selektor schreiben und lesen. In dem Fall sind Unions
sicher die eleganteste und am wenigsten fehlerträchtige Möglichkeit der
Umsetzung.
Markus F. schrieb:> Bitfelder sind nicht portabel (eben weil implementation defined).
Bitfelder sind portabel, nur die Zuordnung zu irgendwelchen Bits ist
implmenetation-defined, sprich etwa bei Serialisierung unbrauchbar.
> Wenn ich das richtig verstehe, will der TO den Wochentag sowieso *immer*> über den Union-Selektor schreiben und lesen. In dem Fall sind Unions> sicher die eleganteste und am wenigsten fehlerträchtige Möglichkeit der> Umsetzung.
Könnte sein, war aber bislang irgendwie nicht zu erkennen aus dem Post
des TO. Dann ist der Zweck der union aber wirklich nur das Sparen von
Speicher (gut) und nicht das type-punning (schlecht). Wobei er uns auch
die Diskriminatorlogik verschwiegen hat ...
Oh, trockene Kost.
Ich weiß ja, mir fehlt zum Informatiker etliches. Weshalb ich gerne
besagte Diskriminatorlogik offenbaren würde, könnte ich irgendwo lernen,
was das ist.
Dazu aber läßt mir das schnöde Berufsleben derzeit keine/sehr sehr wenig
Zeit.
Diesen (De-)Serialisierungsarktikel zieh ich mir aber noch mal rein.
Aber später. Nur, Typumwandlungen per Shiften und Maskieren, kostet
Programmieraufwand, Flash und Zyklen(was beim aktuellen Projekt aber
untergeordnet wäre). Deshalb war mir die Union-Geschichte ja so
sympathisch.
BL schrieb:> Nur, Typumwandlungen per Shiften und Maskieren, kostet> Programmieraufwand
Dafür weniger Aufwand bei der Fehlersuche und Pflege, wenn du mal
irgendwas an der Umgebung änderst (Compiler-Optionen, -Version,
-Hersteller, Prozessor, ...). Je später Korrekturen im Projekt
vorgenommen werden, desto teurer werden sie bekanntlich. Daher am Besten
gleich richtig machen.
BL schrieb:> Flash und Zyklen
Gute Compiler optimieren das weg. Probiere es erst aus, bevor du das als
zu langsam abstempelst.
BL schrieb:> Deshalb war mir die Union-Geschichte ja so> sympathisch.
Da hier Speicher-Zugriffe quasi erzwungen werden, kann das sogar
langsamer sein. unions werden hier für etwas missbraucht für das sie
nicht da sind, das finden die bestimmt nicht so sympathisch!
BL schrieb:> Ich weiß ja, mir fehlt zum Informatiker etliches
Dann hör auf die "richtigen" Informatiker und ihre Ratschläge...
Wilhelm M. schrieb:> bei C: implementation-defined behaviour
Das ist ja auch nicht schlimm. Es gibt in konkreten Projekten halt
konkrete Implementierungen. Darum muss man sich "erst" beim
Plattformwechsel oder Compiler-Update kümmern. Mach ein
Compiletime-Assert dran und fertig.
--> wenn mein (konkreter) Applikations-Code mit Unions deutlich klarer
wird, nehme ich Unions. Mit CT-Assert. Der Preis ist dann
gerechtfertigt.
--> wenn mein Treibercode von anderen Leuten (oder von mir) auf
verschiedenen Plattformen eingesetzt wird, mache ich es portabel. Den
Preis würden nämlich andere zahlen müssen (und sie werden es nicht tun!)
Man muss die Kirche im Dorf lassen. Wichtig ist nur, zu wissen, was man
tut (den Preis kennen und es ggf. absichern).
Achim S. schrieb:> Wilhelm M. schrieb:>> bei C: implementation-defined behaviour>> Das ist ja auch nicht schlimm. Es gibt in konkreten Projekten halt> konkrete Implementierungen. Darum muss man sich "erst" beim> Plattformwechsel oder Compiler-Update kümmern. Mach ein> Compiletime-Assert dran und fertig.>> --> wenn mein (konkreter) Applikations-Code mit Unions deutlich klarer> wird, nehme ich Unions. Mit CT-Assert. Der Preis ist dann> gerechtfertigt.>> --> wenn mein Treibercode von anderen Leuten (oder von mir) auf> verschiedenen Plattformen eingesetzt wird, mache ich es portabel. Den> Preis würden nämlich andere zahlen müssen (und sie werden es nicht tun!)>> Man muss die Kirche im Dorf lassen. Wichtig ist nur, zu wissen, was man> tut (den Preis kennen und es ggf. absichern).
Kann man das bitte auf der Startseite anschlagen?
Wenn man den Text noch auf "goto" und "malloc" ausweitet, dann kann man
jeden 3.Thread zu C quasi direkt löschen.
BL schrieb:> Oh, trockene Kost.>> Ich weiß ja, mir fehlt zum Informatiker etliches. Weshalb ich gerne> besagte Diskriminatorlogik offenbaren würde, könnte ich irgendwo lernen,> was das ist.
Dein Code wäre ok, wenn Du bei Objekten dieses Typs immer nur eines der
union-Elemente verwendest:
1
typedefunion
2
{
3
unsignedshortlonguns24;
4
unsignedcharv[3];
5
struct
6
{
7
unsignedcharsekunde;
8
unsignedcharminute;
9
unsignedcharstunde;
10
};
11
}S_M_H_Time;
12
13
...
14
15
S_M_H_Timea;
16
S_M_H_Timeb;
17
18
...
19
20
a.uns24=...;// niemals etwas anderes von a verwenden als uns24
21
22
b.sekunde=...;// niemals etwas anderes von b verwenden als sekunde,minute oder stunde
Du sprachst von einem Array, also etwa:
1
S_M_H_Timearray[10];
Wenn Du nun sicherstellen kannst, dass etwa das Element array[0] immer
nur als array[0].uns24 und etwa array[1].sekunde (s.o.) verwendet wird,
ist es auch ok.
Wenn aber über das array iterierst, muss Du ja irgendwie entscheiden,
als was Du das array-Element verwendest: das ist die Diskriminatorlogik
dann.
(Sorry für den Hinweis: std::variant<> hat diese Logik eingebaut)
> Dazu aber läßt mir das schnöde Berufsleben derzeit keine/sehr sehr wenig> Zeit.
Das würde ich versuchen zu ändern ;-)
Heiko L. schrieb:> Undefined oder nicht?
Undefined in C++, Implementation defined in C, denn das Ergebnis in u16
hängt von der Endianness der Plattform ab.
Heiko L. schrieb:> Woher kommt das undefined?
Weil in C++ die DT in der union auch UDT sein können. Diese haben im
allgemeinen ctor'en bzw. dtor'en. Beim Wechsel des aktiven Elementes in
der union müssten dann eigentlich die ctor'en bzw. dtor'en aufgerufen
werden. Das findet lt. Standard aber nicht statt. Insofern würde der
Wechsel des aktiven Elementes zu einem uninitialisierten ELement führen.
Und das ist UB.
Wilhelm M. schrieb:> Heiko L. schrieb:>> Woher kommt das undefined?>> Weil in C++ die DT in der union auch UDT sein können. Diese haben im> allgemeinen ctor'en bzw. dtor'en. Beim Wechsel des aktiven Elementes in> der union müssten dann eigentlich die ctor'en bzw. dtor'en aufgerufen> werden. Das findet lt. Standard aber nicht statt. Insofern würde der> Wechsel des aktiven Elementes zu einem uninitialisierten ELement führen.> Und das ist UB.
Das trifft hier aber gar nicht zu:
If members of a union *are classes with user-defined constructors and
destructors*, to switch the active member, explicit destructor and
placement new are generally needed
Wilhelm M. schrieb:> Heiko L. schrieb:>> Woher kommt das undefined?>> Weil in C++ die DT in der union auch UDT sein können. Diese haben im> allgemeinen ctor'en bzw. dtor'en. Beim Wechsel des aktiven Elementes in> der union müssten dann eigentlich die ctor'en bzw. dtor'en aufgerufen> werden. Das findet lt. Standard aber nicht statt. Insofern würde der> Wechsel des aktiven Elementes zu einem uninitialisierten ELement führen.> Und das ist UB.
Ich habe jetzt nicht im Standard nachgelesen, aber laut
http://en.cppreference.com/w/cpp/string/byte/memmove ist memmove OK,
wenn der DT TriviallyCopyable ist. Ich würde memmove hier nicht nutzen,
ohne vorher sicherzugehen, dass strict aliasing nicht verletzt wird.
mh schrieb:> Wilhelm M. schrieb:>> Heiko L. schrieb:>>> Woher kommt das undefined?>>>> Weil in C++ die DT in der union auch UDT sein können. Diese haben im>> allgemeinen ctor'en bzw. dtor'en. Beim Wechsel des aktiven Elementes in>> der union müssten dann eigentlich die ctor'en bzw. dtor'en aufgerufen>> werden. Das findet lt. Standard aber nicht statt. Insofern würde der>> Wechsel des aktiven Elementes zu einem uninitialisierten ELement führen.>> Und das ist UB.>> Ich habe jetzt nicht im Standard nachgelesen, aber laut> http://en.cppreference.com/w/cpp/string/byte/memmove ist memmove OK,> wenn der DT TriviallyCopyable ist. Ich würde memmove hier nicht nutzen,> ohne vorher sicherzugehen, dass strict aliasing nicht verletzt wird.
Da memmove nicht explizit genannt wird, um das active member auch bei
trivially copyable objekten genannt wird, würde ich es als UB werten: Du
liest von einem nicht active member, was keine common initail sequence
mit dem active member hat.
BL schrieb:> Ich habe eine Union für die Uhrzeit gemacht.>....> In die wird ein RTCC reingelesen und das funktioniert soweit auch alles.
Soweit...
Ich halte deine gesamte Vorgehensweise für falsch. Was willst du
eigentlich mit solchem Union machen? Du hast nur dran gedacht, den
Inhalt deines RTC irgendwohin zu verfrachten - aber nicht daran, daß
diese Daten ja anschließend verwendet werden sollen. Und das geht mit
solchem Union eben miserabel bis hin zur Bauchlandung.
Du hättest es von Anfang an anders machen sollen: Ab irgend einem
sinnvollen Datum, z.B. 1.1.2000 oder 1.1.1970 die Zeit in Sekunden
zählen und dies in einem long speichern (reicht für etwa +/-68 Jahre)
oder in einem int64 (reicht fast ewig).
Mit deinen vorhandenen 24 Bit kannst du nur die
Tage,Stunden,Minuten,Sekunden zählen. Das reicht nicht mal für ein Jahr
(24 Bit macht 16 Mio, brauchen würdest du ca. 32 Mio), aber für ne Woche
reicht es allemal.
Also Zeit = ((Wochentag * 24 + Stunden) * 60 + Minuten) * 60 + Sekunden;
Das BLÖDE daran ist, daß ein Integer von 24 Bit sowas von unüblich
ist, daß sich einem der Magen umdreht. Also bleibt dir bloß
Wilhelm M. schrieb:> mh schrieb:>> Wilhelm M. schrieb:>>> Heiko L. schrieb:>>>> Woher kommt das undefined?>>>>>> Weil in C++ die DT in der union auch UDT sein können. Diese haben im>>> allgemeinen ctor'en bzw. dtor'en. Beim Wechsel des aktiven Elementes in>>> der union müssten dann eigentlich die ctor'en bzw. dtor'en aufgerufen>>> werden. Das findet lt. Standard aber nicht statt. Insofern würde der>>> Wechsel des aktiven Elementes zu einem uninitialisierten ELement führen.>>> Und das ist UB.>>>> Ich habe jetzt nicht im Standard nachgelesen, aber laut>> http://en.cppreference.com/w/cpp/string/byte/memmove ist memmove OK,>> wenn der DT TriviallyCopyable ist. Ich würde memmove hier nicht nutzen,>> ohne vorher sicherzugehen, dass strict aliasing nicht verletzt wird.>> Da memmove nicht explizit genannt wird, um das active member auch bei> trivially copyable objekten genannt wird, würde ich es als UB werten: Du> liest von einem nicht active member, was keine common initail sequence> mit dem active member hat.
Sollte heissen:
Da memmove nicht explizit genannt wird, um das active member auch bei
trivially copyable objekten zu wechseln ...
Wilhelm M. schrieb:> Da memmove nicht explizit genannt wird, um das active member auch bei> trivially copyable objekten genannt wird, würde ich es als UB werten: Du> liest von einem nicht active member, was keine common initail sequence> mit dem active member hat.
Aus dem C11 Standard zu memmove:
1
The memmove function copies n characters from the object pointed to by s2 into the
2
object pointed to by s1. Copying takes place as if the n characters from the object
3
pointed to by s2 are first copied into a temporary array of n characters that does not
4
overlap the objects pointed to by s1 and s2, and then the n characters from the
5
temporary array are copied into the object pointed to by s1.
Das Ergebnis von memmove ist also "as if" erst aus der Quelle in einen
Zwischenspeicher kopiert wird und dann aus dem Zwischenspeicher ins
Ziel. Es wird also nicht auf beide member der union zugeriffen.
Für mich ist es grad zu spät, um mich genauer mit den Lifetime Regeln
von unions im C++ Standard auseinanderzusetzen...
Hast du vllt. Zeit und Interesse daran herauszufinden, was clangs
UB-sanitizer dazu sagt? ;-)
mh schrieb:> Hast du vllt. Zeit und Interesse daran herauszufinden, was clangs> UB-sanitizer dazu sagt? ;-)
Also ich habe es gerade auprobiert und bin damit auch echt
durchgekommen. Oder muss man noch mehr als "-fsanitize=undefined"
angeben? Bin die Liste durchgegangen, es klang aber nichts sonderlich
passend.
Was ist eigentlich daraus geworden, das ein char immer aliasen darf und
deshalb eine Union auch dazu verwendet werden darf von Typ x nach char
Array und zurück zu wandeln?
GCC meckert diesen Fall auch bei strict aliasing Rules nicht an.
Karl schrieb:> deshalb eine Union auch dazu verwendet werden darf von Typ x nach char> Array und zurück zu wandeln?
Diese Regel gab es im Standard nie.
Wilhelm M. schrieb:> Markus F. schrieb:>> Bitfelder sind nicht portabel (eben weil implementation defined).>> Bitfelder sind portabel, nur die Zuordnung zu irgendwelchen Bits ist> implmenetation-defined, sprich etwa bei Serialisierung unbrauchbar.
Ja, genau. Bitfelder sind portabel, solange man sie so nutzt, wie es
vorgesehen ist. Das gleiche gilt für unions. Das hier ist aber
Missbrauch sowohl von Bitfeldern, als auch von Unions.
BL schrieb:> Diesen (De-)Serialisierungsarktikel zieh ich mir aber noch mal rein.> Aber später. Nur, Typumwandlungen per Shiften und Maskieren, kostet> Programmieraufwand, Flash und Zyklen(was beim aktuellen Projekt aber> untergeordnet wäre). Deshalb war mir die Union-Geschichte ja so> sympathisch.
Und du meinst, bei der Union-Variante extrahiert der Prozessor die Bits
per Magie da raus? Der muss natürlich die gleichen Operationen machen,
nur sind sie dann versteckt.
Achim S. schrieb:> Wilhelm M. schrieb:>> bei C: implementation-defined behaviour>> Das ist ja auch nicht schlimm. Es gibt in konkreten Projekten halt> konkrete Implementierungen. Darum muss man sich "erst" beim> Plattformwechsel oder Compiler-Update kümmern. Mach ein> Compiletime-Assert dran und fertig.
Das sehe ich etwas anders. Wenn man sich dann irgendwann entscheidet,
das Programm zu portieren, hat man Tausende von unportablen Konstrukten
im Programm, und die Portierung ist ein riesen Aufwand.
Wenn man dagegen gleich etwas darauf achtet, die Sachen portabel zu
halten, kostet das meistens kaum Zusatzaufwand, und das Portieren ist
nachher erheblich einfacher.
Rolf M. schrieb:> Das sehe ich etwas anders. Wenn man sich dann irgendwann entscheidet,> das Programm zu portieren, hat man Tausende von unportablen Konstrukten> im Programm, und die Portierung ist ein riesen Aufwand.
Die Chance, daß das auf einer anderen Architektur oder mit einem anderen
Compiler auch funktioniert, ist allerdings nicht so ganz klein.
Ich sehe den generellen Schwachpunkt der ganzen Geschichte eher hier:
BL schrieb:> Die Instanz meiner Union ist Bestandteil eines großen Arrays, in dem ich> nichts mehr verschieben kann. Ein 4.Byte an die Union hängen, geht daher> nicht.
Das fliegt denen mit Sicherheit noch mehrfach um die Ohren, vor allem,
wenn „der Kunde“ sein Anforderungen noch häufiger ändert.
Oliver
Rolf M. schrieb:> Und du meinst, bei der Union-Variante extrahiert der Prozessor die Bits> per Magie da raus? Der muss natürlich die gleichen Operationen machen,> nur sind sie dann versteckt.
Ich finde es schon komisch, dass der C++-Way-to-do-it ist, eine
C-Funktion zu verwenden...
Heiko L. schrieb:> Ich finde es schon komisch, dass der C++-Way-to-do-it ist, eine> C-Funktion zu verwenden...
Die meisten C Bestandteile sind auch in C++ sehr wichtig. Für Funktionen
und arithmetische Berechnungen hat C++ auch keinen Ersatz, nur
Erweiterungen.
Karl schrieb:> Ma W. schrieb:>> Diese Regel gab es im Standard nie.>> Ich meine schon. Schau bei Gelegenheit nach.
Du meinst wahrscheinlich, dass char* zu jedem anderen T* als alias
betrachtet wird und man deswegen die Objektrepräsentation anderer Typen
byteweise durch einen char* lesen darf. So (frei übersetzt) steht es im
Standard (die Umkehrung gilt i.d.R. nicht).
Wilhelm M. schrieb:> Du meinst wahrscheinlich, dass char* zu jedem anderen T* als alias> betrachtet wird und man deswegen die Objektrepräsentation anderer Typen> byteweise durch einen char* lesen darf. So (frei übersetzt) steht es im> Standard (die Umkehrung gilt i.d.R. nicht).
Keine Ahnung. Meine ich das?
Folgender Auszug lässt mich anders denken.
1
If a value is copied into an object having no declared type using
2
memcpy or memmove, or is copied as an array of character type, then the effective type
3
of the modified object for that access and for subsequent accesses that do not modify the
4
value is the effective type of the object from which the value is copied, if it has one. For
5
all other accesses to an object having no declared type, the effective type of the object is
6
simply the type of the lvalue used for the access.
7
8
9
7 An object shall have its stored value accessed only by an lvalue expression that has one of
10
the following types:76)
11
— a type compatible with the effective type of the object,
12
— a qualified version of a type compatible with the effective type of the object,
13
— a type that is the signed or unsigned type corresponding to the effective type of the
14
object,
15
— a type that is the signed or unsigned type corresponding to a qualified version of the
16
effective type of the object,
17
— an aggregate or union type that includes one of the aforementioned types among its
18
members (including, recursively,amember of a subaggregate or contained union), or