Hallo!
Hat jemand eine Ahnung was das folgende genau machen soll (Code-Auszug
aus einem kleinen Robotorprojekt mit 16Bit µC):
-------------------------------------------
union Long_Word { long tog;
struct LSWMSW det;};
struct LSWMSW { int frst; int scnd; };
union Long_Word filter1;
int rob_acc;
filter1.tog = 4294901760; // ( Irgendeine Wertübergabe als long aus
einem Macro)
rob_acc = filter1.det.scnd;
---------------------------------------------
filter1.det.scnd ist doch überhaupt nicht mit einem Wert beschrieben?
Bzw. ich verstehe nicht, was hier bezwegt werden soll. Habe das auch
schon als printf simuliert. Wenn filter1.det.scnd mit 0 intitialisiert
wird, dann bleibt auch rob_acc 0. Also was macht dann filter1.tog? Hier
soll ja irgendwie eine Wertübergabe (casten?) von filter1.tog an rob_acc
erfolgen?
Danke schon mal vorab...
Peter L. schrieb:> filter1.det.scnd ist doch überhaupt nicht mit einem Wert beschrieben?
Deshalb ist das, was per filter1.det.scnd gelesen wird, auch undefiniert
gemäß dem C-Standard.
Peter L. schrieb:> filter1.det.scnd ist doch überhaupt nicht mit einem Wert beschrieben?
Wenn vorher wirklich nichts anderes mit filter1 angestellt wurde und der
Schnipsel, den Du da als Code angibst, vollständig ist, ja.
Peter L. schrieb:> struct LSWMSW { int frst; int scnd; };
Der Code hat offensichtlich den Zweck, den Leser maximal zu verwirren
und zu ärgern. Aus keinem anderen Grund würde man solche
Variablennamen verwenden! Derjenige, der den Code geschrieben hat,
gehört maximal geprügelt.
Was Dir wahrscheinlich nicht bewusst ist: Die Elemente der Union liegen
übereinander im Speicher, sie teilen sich also die gleichen
Speicherzellen.
Ein int ist auf der Plattform wahrscheinlich 16 Bit breit, ein long 32
Bit. Mit filter1.tog setzt man also gleichzeitig filter1.det.frst und
filter1.det.scnd.
Welche Bytes wo sind, kann man anhand des Codes allerdings nicht sagen.
Erstens ist streng genommen nicht gesagt, wie der Compiler frst und scnd
in LSWMSW anordnet. Zweitens hängt es davon ab, wie auf der Plattform
allgemein die Bytes in einem int/long angeordnet sind (Endianness).
Der Code ist also ein hervorragendes Negativbeispiel, wie man Unions
nicht benutzten sollte. Von der Wahl der Bezeichner, der
Codeformatierung und dem Zuweisen eines Dezimal- statt Hexwertes ganz
abgesehen ...
Nein, das funktioniert schon so. Deshalb ist das ja ein union. In
Long_Word belegen der long mit dem geschickten Namen "tog" und die
"struct LSWMSM det" denselben Speicher. "filter1.det.scnd" gibt dir also
"die zweite Hälfte" von dem Long, der da vorher reingeschrieben wurde.
Diese lange Zahl macht wahrscheinlich mehr Sinn, wenn man sie als
Bitmaske interpretiert.
Aber ja, für die Variablennamen gehört derjenige, der das geschrieben
hat, mal ein bisschen getreten ;)
Und plattformunabhängig ist es auch nicht.
Grüße,
Sven
Fabian O. schrieb:> Ein int ist auf der Plattform wahrscheinlich 16 Bit breit, ein long 32> Bit. Mit filter1.tog setzt man also gleichzeitig filter1.det.frst und> filter1.det.scnd.
Das ist undefined Behavior in C.
Funktioniert, funktioniert vielleicht auch nicht...
Ditto:
Sven B. schrieb:> Nein, das funktioniert schon so. Deshalb ist das ja ein union. In> Long_Word belegen der long mit dem geschickten Namen "tog" und die> "struct LSWMSM det" denselben Speicher. "filter1.det.scnd" gibt dir also> "die zweite Hälfte" von dem Long,
Nein, tut es nicht.
filter1.det.scnd liefert irgendwas.
Scheint schon irgendwie zu funktionieren, da rob_acc für mich einen
plausiblen Wert anzeigt, wenn dieser im Target läuft. Aber wenn ich den
oben gezeigten Codeabschnitt in der Windowsconsole laufen lasse dann
ergibt das für mich alles keinen Sinn...(auch wenn die Console hier mit
windows xp läuft).
Wie gesagt, wenn ich filter1.det.scnd mit Null vor dem filter1.tog =
4294901760 (was einfach eine X-beliebe Zahl sein soll) initialisiere,
dann ist hier auch rob_acc null.
Aber schön, dass hier alle der gleichen Meinung sind :-)
Johann L. schrieb:> filter1.det.scnd liefert irgendwas.Peter L. schrieb:> Scheint schon irgendwie zu funktionieren, da rob_acc für mich einen> plausiblen Wert anzeigt, wenn dieser im Target läuft.
Das Konstrukt ist abhängig von der Implementierung. Wenn du immer
denselben Compiler udn dieselbe Zielhardware benutzt, dann ist das kein
Problem.
Probleme kann es - muß es aber nicht - geben, wenn dur dein Programm in
einen andere Umgebung portierst.
C ist eben eine pragmatisch entworfenen Sprache...
Johann L. schrieb:> filter1.det.scnd liefert irgendwas.
Es liefert nicht irgendwas. Es liefert immer dasselbe, wenn Du eine
Plattform benutzt, auf der int und long auf eine bestimmte Art aufgebaut
sind (Endianness + Größe).
Natürlich ist es kein guter Code, weil nicht plattformunabhängig, aber
wenn Du auf einer Plattform bist, wo Du weißt, wie int und long
aussehen, dann funktioniert es.
Undefined Behaviour ist es nicht, es wird zum Beispiel immer kompilieren
und nicht abstürzen.
Peter L. schrieb:> Scheint schon irgendwie zu funktionieren, da rob_acc für mich einen> plausiblen Wert anzeigt, wenn dieser im Target läuft. Aber wenn ich den> oben gezeigten Codeabschnitt in der Windowsconsole laufen lasse dann> ergibt das für mich alles keinen Sinn...(auch wenn die Console hier mit> windows xp läuft).
Genau das ist ja das Problem, nämlich dass der Code nicht auf andere
Hardware portabel ist. Wenn Du ihn unter Windows laufen lässt, dann ist
ein int 32 Bit breit statt 16 Bit auf dem Mikrocontroller.
Also teilen sich dann filter1.det.frst und filter1.tog die gleichen 32
Bit im Speicher und filter1.det.scnd nimmt die nächsten 32 Bit ein.
Zumindest üblicherweise, streng genommen darf der Compiler es wie gesagt
auch anders machen.
Dann passt auch dieses Verhalten:
> Wie gesagt, wenn ich filter1.det.scnd mit Null vor dem filter1.tog => 4294901760 (was einfach eine X-beliebe Zahl sein soll) initialisiere,> dann ist hier auch rob_acc null.
Die 4294901760 stehen in den ersten 32 Bit (filter1.det.frst bzw.
filter1.tog) und in den anderen 32 Bit (filter1.det.scnd) steht Null.
Auf dem Mikrcontroller sind es dagegen 16 Bit für filter1.det.frst und
16 Bit für filter1.det.scnd und filter1.tog entspricht beiden Variablen
zu 32 Bit kombiniert.
Ja, stimmt. Das ist natrülich nicht eins zu eins portierbar.
Generell erreicht man über diese verwirrenden Strukturkombinationen
letzendlich nichts anderes als ein Casten von long (32bit) auf int
(16bit) (im 16bit Target)??
Danke für euere Beiträge!
Sven B. schrieb:> Johann L. schrieb:>> filter1.det.scnd liefert irgendwas.>> Es liefert nicht irgendwas. Es liefert immer dasselbe, wenn Du eine> Plattform benutzt, auf der int und long auf eine bestimmte Art aufgebaut> sind (Endianness + Größe).
Zeig mir die Stelle im C-Standard!
1
int foo (void)
2
{
3
union { int a, b; } u;
4
u.a = 42;
5
return u.b;
6
}
Hier wird u.b nie beschrieben und u.a nie gelesen. Weil u.a nie gelesen
wird, darf der Compiler er wegwerfen. Der Code ist also i.w.
gleichbedeiutend mit:
1
int foo (void)
2
{
3
int b;
4
return b;
5
}
Der Standard sichert dir zu, die zuletzt geschriebene Komponente beim
Zugrif auf diese zu liefern. Sonst nix. Weil u.b nicht die "zuletzt
geschriebene Komponente ist", hat es laut Standard keinen definierten
Wert.
Peter L. schrieb:> Generell erreicht man über diese verwirrenden Strukturkombinationen> letzendlich nichts anderes als ein Casten von long (32bit) auf int> (16bit) (im 16bit Target)??
Naja, nicht ganz. Beim Casten bleiben nur die niederwertigen Bits
erhalten.
In Hex-Schreibweise ist es klarer:
1
filter1.tog=4294901760;
ist das gleiche wie
1
filter1.tog=0xFFFF0000;
oder binär:
1
filter1.tog=0b11111111111111110000000000000000;
Die höherwertigen 16 Bit sind also 1, die niederwertigen 16 Bit 0. Wenn
wir mal davon ausgehen, dass Dein Mikrocontroller eine
Big-Endian-Maschine ist und die Struktur so angeordnet ist, wie man es
üblicherweise annimmt, liegen die Bits so im Speicher:
11111111111111110000000000000000
| det.frst || det.scnd |
+------------------------------+
| tog |
Mit det.frst kannst Du also auf die oberen 16 Bit von tog zugreifen und
mit det.scnd auf die unteren 16 Bit.
Wenn man den gleichen Variableninhalt ohne Union erreichen will, wäre es
also folgendes:
1
filter.det.frst=0xFFFF;
2
filter.det.scnd=0x0000;
Und wenn man beide aus einer 32-Bit-Variable beschreiben will:
1
longvar=0xFFFF0000;
oder wenn es sein muss
1
longvar=4294901760;
Dann kann man auf diese Weise die 32-Bit-Variable in zwei mal 16 Bit
zerlegen:
1
filter.det.frst=(var>>16)&0xFFFF;
2
filter.det.scnd=var&0xFFFF;
So behält man die Kontrolle, welche Bits wo hin kommen und es ist nicht
mehr von der Endianness und Breite eines int abhängig.
Die Größe von einem union ist die Größe seines größten Members. Der
Standard garantiert, dass die Elemente alle zum Anfang vom Union aligned
sind. Ein Zugriff auf ein Element vom Typ Foo in dem Union nimmt die
ersten sizeof(Foo) Bytes aus dem Union und interpretiert sie als Instanz
von Foo. Solange Du also
-> den internen Aufbau der Typen in dem Union (z.B. Endianness)
-> und sizeof() der Typen
als fest gegeben voraussetzt, kannst Du mit dem Standard vorhersagen,
was passieren wird. Insofern verstehe ich das Problem nicht ganz. Dein
Bespiel beschreibt ja nur eine (korrekte) Optimierung, die der Compiler
vornimmt.
> Dann kann man auf diese Weise die 32-Bit-Variable> in zwei mal 16 Bit zerlegen:
So würde ich das auch machen, ja. Ich denke, das Problem bei der Methode
ist, dass der uc rechnen muss, falls der Compiler das nicht vernünftig
wegoptimiert.
Grüße,
Sven
@Fabian: Ich denke ich habs jetzt so langsam geschnallt. Danke für deine
super Darstellung.
Es ist little edian. Also wäre det.scnd die 1er bits und det.fst die
0er bits.
Sven B. schrieb:> Ein Zugriff auf ein Element vom Typ Foo in dem Union nimmt die> ersten sizeof(Foo) Bytes aus dem Union und interpretiert sie als Instanz> von Foo.
Papperlapapp.
Aus dem C-Standard (C99), 6.2.6.1:
> When a value is stored in a member of an object of union type,> the bytes of the object representation that do not correspond> to that member but do correspond to other members take unspecified> values,
Das ist ziemlich eindeutig! Schreibst Du in a_union.a etwas rein, ist
anschließend der Inhalt von a_union.b undefiniert. Ganz einfach.
Und in 6.7.2.1 steht es auch:
> The size of a union is sufficient to contain the largest of> its members. The value of at most one of the members can be> stored in a union object at any time.
Es ist nicht mal definiert, dass alle Union-Elemente am Anfang des
Objektes liegen! Bei Strukturen ist es in der Tat definiert, dass es am
Beginn kein Padding gibt, bei Unions aber nicht!
Ja, diesen union-Murks zum casten sieht man immer wieder mal. Ändert
aber nichts an der Tatsache, dass es einfach nur falsch ist.
> Solange Du also> -> den internen Aufbau der Typen in dem Union (z.B. Endianness)> -> und sizeof() der Typen> als fest gegeben voraussetzt, kannst Du mit dem Standard vorhersagen,> was passieren wird.
Fundstelle im C-Standard bitte zitieren.
Unbekannt Unbekannt schrieb:>> When a value is stored in a member of an object of union type,>> the bytes of the object representation that do not correspond>> to that member but do correspond to other members take unspecified>> values,>> Das ist ziemlich eindeutig! Schreibst Du in a_union.a etwas rein, ist> anschließend der Inhalt von a_union.b undefiniert. Ganz einfach.
Nö. Was steht da genau?
> the bytes of the object representation that do not> correspond to that member but do correspond to other members
Da steht, daß beim Schreiben auf ein Element der Union die Bytes, die
nicht zu diesem, aber zu anderen Elementen gehören, undefiniert sind.
Wann aber ist das der Fall?
Wenn man eine union aus zwei unterschiedlich großen Elementen hat, ein
char und ein int (beispielsweise):
1
union
2
{
3
inta;
4
charb;
5
}x;
6
7
x.a=1234;
8
9
x.b='a';
Das Problem tritt hier nur bei der Zuweisung an das Element b auf, weil
das weniger Speicher belegt als das Element a, und also nur die Anzahl
der von Element b benötigten Bytes in Element a definiert verändert
werden.