Forum: Offtopic Programmierung von Strukturen in C


von Peter L. (peter_l92)


Lesenswert?

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...

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

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.

von Da D. (dieter)


Lesenswert?

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.

von Fabian O. (xfr)


Lesenswert?

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 ...

von Sven B. (scummos)


Lesenswert?

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

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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.

von Peter L. (peter_l92)


Lesenswert?

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 :-)

von Uhu U. (uhu)


Lesenswert?

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...

von Sven B. (scummos)


Lesenswert?

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.

von Fabian O. (xfr)


Lesenswert?

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.

von Peter L. (peter_l92)


Lesenswert?

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!

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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.

von Fabian O. (xfr)


Lesenswert?

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
long var = 0xFFFF0000;
oder wenn es sein muss
1
long var = 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.

von Sven B. (scummos)


Lesenswert?

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

von Peter L. (peter_l92)


Lesenswert?

@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.

von Unbekannt U. (Gast)


Lesenswert?

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.

von Sven B. (scummos)


Lesenswert?

Na gut, ich gebe mich geschlagen ;)

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

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
  int a;
4
  char b;
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.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.