Der Effekt ist, dass lightdimm[d].target und dmx_value[d]
unterschiedliche Werte haben, obwohl doch vom gleichen Wert aus
zugeweisen.
Ich habe dann probehalber mal den Code umformuliert und eine lokale
Variable verwendet, dann gehts:
Meiner Meinung nach schiebt der Compiler an der Stelle 6542 beim Befehl
lightdimm[d].target = HIGH8(lightdimm[d].volume); das nicht
inkrementierte r23 nach target und nicht wie bei der okay-Version die
inkrementierte Variable.
mfg Wolfgang
Wolfgang K. schrieb:> #define HIGH8(X) (((t_data16*)&(X))->high8)> #define LOW8(X) (((t_data16*)&(X))->low8)
Diese Methode ist unzulässig. Stichwort Aliasing.
Der Compiler darf, vereinfacht(*) gesagt, davon ausgehen, dass auf eine
Variable vom Typ T in indirekter Form nur über Pointer auf diesen Typ T
zugegriffen wird, nicht aber über Pointer auf andere Typen. Hier ist es
aber ein union*, der auf Teile vom Objekt X zugreift.
*: Wechsel von signed/unsigned ist erlaubt, ebenso char*.
Oliver schrieb:> Das ist zwar prinzipiell richtig, beim AVR aber kein Problem.
Ganz sicher? Oder wars früher bloss keines?
Der gezeigte Code spricht eine andere Sprache. Der passt nämlich genau
zu der Annahme, dass der Compiler die Zuweisung unmittelbar davor als
für die kritische Zeile nicht relevant betrachtet. Dank der
Aliasing-Regeln darf er das.
Ist beim avr-gcc -fno-strict-aliasing implizit aktiv? Kann man ansonsten
mal ausprobieren.
Oliver schrieb:> A. K. schrieb:>> Diese Methode ist unzulässig. Stichwort Aliasing.>> Das ist zwar prinzipiell richtig, beim AVR aber kein Problem.
Was hat Aliasing mit dem Target zu tun? GCC mach Aliasing-Analyse
unabhängig vom Target.
Ich hab schon Code aus "renommierten" Quellen gesehen, die Kommentare
enthielten wie "This module must be compilesd with optimization turned
off because of a GCC bug".
3x darfst du raten wo der Fehler war :-)
Da sollte ne Warnung kommen wie "dereferencing type punned pointer
breaks strict aliasing rules", denn ein t_data16* ist kein Alias für
einen (vermutlich) int*, unsigned*, uint16_t* etc.
Um zu beurteilen, ob es sich um einen GCC-Bug handelt, ist zumindest
folgendes notwendig:
- Optionen, mit dem das Programm übersetzt wurde. Und "Optionen" meint
dabei wirklich Optionen, nicht irgdendwelche Makefile-Schnippel.
- Version des Compilers.
- Präprozessierte Quelle
All dies erhält man, indem man zu den Optionen -save-temps -v hinzufügt
und Ausgabe des Compiles verfügbar macht, d.h. i-Datei und
Consolenausgabe.
Beispiel für Aliasing:
1
intali(void)
2
{
3
inti=0;
4
*(float*)&i=123.;
5
returni;/* Compiler darf i = 0 als Ergebnis liefern */
Hallo,
erst mal danke für die Antworten. Ich denke, ich habe das Problem
verstanden und gehe jetzt halt über eine Wertzuweisung innerhalb der
union. Eigentlich müßte das Problem doch massenhaft auftreten, immer
wenn ich irgendwie die Endianess auflösen muß, brauche ich doch im
Prinzip dieses Aliasing. Ich habe eine bischen gelesen, man hat wohl
eine Ausnahme bei Umweg über (char *) reingebaut ...
Übrigens, dieses HIGH8() habe ich vermutlich aus einer Funktionen des
ASF entlehnt.
Compiler ist avr-gcc (AVR_8_bit_GNU_Toolchain_3.4.4._1229) 4.8.1.
-save-temps kann ich noch nachreichen, ich habe nur schon komplett
umgebaut.
mfg Wolfgang
Wolfgang K. schrieb:> Eigentlich müßte das Problem doch massenhaft auftreten
Tut es, aber keiner wendet die korrekte Lösung an...
> immer> wenn ich irgendwie die Endianess auflösen muß, brauche ich doch im> Prinzip dieses Aliasing.
Nein, brauchst du nicht.
1
uint8_tlow=0xAB;uint8_thigh=0xCD;
2
uint16_tcombined=(high<<8)|low;
Setzt einen 16bit-Integer aus low & high byte Zusammen. Perfekt
definiertes Verhalten, funktioniert garantiert immer, unabhängig davon
ob die CPU Big oder Little Endian ist. Funktioniert auch in die
Gegenrichtung. Moderne Compiler optimieren das auch entsprechend.
Wolfgang K. schrieb:> Hallo,> Eigentlich müßte das Problem doch massenhaft auftreten, immer> wenn ich irgendwie die Endianess auflösen muß, brauche ich doch im> Prinzip dieses Aliasing.
Die klassische Lösung geht über einen direkten Zugriff auf die
Union-member.
Oliver
Oliver schrieb:> Die klassische Lösung geht über einen direkten Zugriff auf die> Union-member.
union's sind nicht zum konvertieren von Daten geeignet, denn:
Wenn man auf einen Member der union etwas geschrieben hat, darf man
nur diesen Member wieder lesen, aber nicht die anderen! Sonst ist
das Ergebnis undefiniert, und macht irgendetwas (was dem Compiler &
Prozessor gerade gefällt), wie der OP festgestellt hat.
1
unionX{
2
uint16_ta;uint8_tb[2];
3
};
4
uint8_ttest(uint16_ti){
5
Xx;x.a=i;
6
returnx.b[0];// FEHLER - Zugriff auf b nicht erlaubt!
7
returnx.a&0xFF;// Zugriff auf a ist erlaubt.
8
}
Der einzige Zweck von union's ist das Sparen von Speicher.
Wolfgang K. schrieb:> ja, stimmt. Ist inzwischen so.
Das war schon immer so. Dass die Compiler optimierungstechnisch so
schlau sind, dass das zum Problem wird, ist noch nicht ganz so lange so.
Wolfgang K. schrieb:> Nur ich kann mich noch erinnern, dass der GCC
schnell vergessen :o)
Wolfgang K. schrieb:> Aber wie gesagt, da hat es riesige Fortschritte gegeben und manch alter> Code muß angepaßt werden.
Wobei ich mich frage, wieviel alter Code "optimiert" war obwohl die
Rechenzeit nie auch nur ansatzweise knapp zu werden drohte... ;-)
Programmierer schrieb:> Wenn man auf einen Member der union etwas geschrieben hat, darf man> nur diesen Member wieder lesen, aber nicht die anderen! Sonst ist> das Ergebnis undefiniert,
Ist es nicht. Es ist "implementation defined".
Oliver
Wolfgang K. schrieb:> Übrigens, dieses HIGH8() habe ich vermutlich aus einer Funktionen des> ASF entlehnt.
Ähnlichen Code kannst du auch in frühem Quellcode des Unix-Kernels
finden. Aber das macht es heute nicht richtiger. Es gab Zeiten, da war
das ok. Später kamen Zeiten, da war es schon nicht mehr ok,
funktionierte aber noch. Und mittlerweile leben wir in Zeiten, in den
der Compiler dir völlig zurecht an Stellen eine Nase dreht, die ewig
funktioniert haben. GCC hat in den letzten Versionen schon manche Leute
bös überrascht.
A. K. schrieb:> GCC hat in den letzten Versionen schon manche Leute> bös überrascht.
Ich würd es so formulieren: Seit manche Leute neuere Versionen von GCC
verwenden werden sie von ihren eigenen Programmen bös überracht. Bzw.
haben anderen ein Ei ins Nest gelegt mit ihrem Code.
Das ist übrigens ein Grund, weshalb man zusammen mit einem Projekt die
dabei verwendete Entwicklungsumgebung einfrieren sollte. Damit man 5
Jahre drauf noch was fixen kann, ohne als Folge zwischenzeitlicher
Compiler-Updates über solche Spässe zu stolpern. Virtuelle Maschinen
sind da ungemein praktisch.
Programmierer schrieb:> union's sind nicht zum konvertieren von Daten geeignet
Wie würdest du eine uint32_t in vier Bytes zerlegen? Ich hab dafür immer
eine Union genommen und nie Probleme gehabt, gibt es eine bessere
Methode?
A. K. schrieb:> Virtuelle Maschinen> sind da ungemein praktisch.
für einen GCC aber etwas übertrieben. Bei mir gibt es in jede Projekt
ein Batchfile, das setzt einfach die passenden Umgebungsvariable zum
GCC.
schon kann ich mehrere Projekte mit unterschiedlichen GCC verwenden.
Jim C schrieb:> Wie würdest du eine uint32_t in vier Bytes zerlegen?
Genauso wie bei 2 Bytes. Nur sind es dann eben 4 Komponenten.
Alternativ kannst du auch char* oder memcpy verwenden. Die sind
hochoffiziell zulässig. Auf PC-CPUs aber u.U. etwas schnarchig.
A. K. schrieb:> Alternativ kannst du auch char* oder memcpy verwenden. Die sind> hochoffiziell zulässig. Auf PC-CPUs aber u.U. etwas schnarchig.
wobei dann aber bei verschieden Architekturen was anderes rauskommt.
Peter II schrieb:> wobei dann aber bei verschieden Architekturen was anderes rauskommt.
Byteorder. Klar. Aber das ist bei obiger Union auch nicht anders.
A. K. schrieb:> Byteorder. Klar. Aber das ist bei obiger Union auch nicht anders.
Bei den Bitshifts aber schon, da kann man von einer festen bekannten
Byte-Order in die Reihenfolge der CPU konvertieren.
GCC ist in der Lage, memcpy durch einzelne Instruktionen zu ersetzen
wenn es passt.
memcpy, unions und casts haben ein Problem: Sie benötigen Pointer auf
die Daten, und einen solchen kann man nur erhalten wenn diese im RAM
sind, aber nicht wenn sie in Registern sind. So zwingt man den Compiler,
die Daten in den RAM zu packen, auch wenn sie zuvor in Registern waren.
Bei Bitshifts kann der Compiler die Berechnung komplett in Registern
abhandeln.
Programmierer schrieb:> memcpy, unions und casts haben ein Problem: Sie benötigen Pointer auf> die Daten, und einen solchen kann man nur erhalten wenn diese im RAM> sind, aber nicht wenn sie in Registern sind.
Es stimmt zwar, dass man formal eine Adresse braucht. Trotzdem kann es
sein, dass der entstehende Code dann doch völlig ohne RAM und ohne
Adresse auskommt. Compiler sind manchmal so schlau.
Aber ich hatte die memcpy und char* Varianten nicht als ultima ratio
beschrieben, sondern als eine von mehreren Möglichkeiten, nicht ohne
unangenehme Nebeneffekte. Bei Integer-Typen ist die arithmetische Lösung
oft vorzuziehen.
Oliver schrieb:> Programmierer schrieb:>> Wenn man auf einen Member der union etwas geschrieben hat, darf man>> nur diesen Member wieder lesen, aber nicht die anderen! Sonst ist>> das Ergebnis undefiniert,>> Ist es nicht. Es ist "implementation defined".
Hast du einen Beleg für diese Behauptung?
Programmierer schrieb:> GCC ist in der Lage, memcpy durch einzelne Instruktionen zu ersetzen> wenn es passt.>> memcpy, unions und casts haben ein Problem: Sie benötigen Pointer auf> die Daten, und einen solchen kann man nur erhalten wenn diese im RAM> sind, aber nicht wenn sie in Registern sind. So zwingt man den Compiler,> die Daten in den RAM zu packen, auch wenn sie zuvor in Registern waren.
Man zwingt den Compiler zu gar nix. Warum sollte er extra die Daten in
den RAM kopieren, nur um sie von dort wieder zurück in Register zu
kopieren? Gerade um sowas zu vermeiden, gibt's einen Optimizer. Zeiger
können wie andere Variablen auch wegoptimiert werden, wenn sie
eigentlich nicht notwendig sind.
Rolf Magnus schrieb:>> memcpy, unions und casts haben ein Problem: Sie benötigen Pointer auf>> die Daten, und einen solchen kann man nur erhalten wenn diese im RAM>> sind, aber nicht wenn sie in Registern sind. So zwingt man den Compiler,>> die Daten in den RAM zu packen, auch wenn sie zuvor in Registern waren.>> Man zwingt den Compiler zu gar nix. Warum sollte er extra die Daten in> den RAM kopieren, nur um sie von dort wieder zurück in Register zu> kopieren?
weil oft memcpy eine Blackbox für den Compiler ist, dieser braucht als
Parameter ein Zeiger und diese kann nicht auf Register zeigen. Damit
zwingt man den Compiler.
Falls der Compiler memcpy kennt, dann könnte er etwas anders rangehen,
aber das ist auch etwas Problematisch falls man ein eigenes memcpy hat
was sicher anders verhält.
Peter II schrieb:> Rolf Magnus schrieb:>> Man zwingt den Compiler zu gar nix. Warum sollte er extra die Daten in>> den RAM kopieren, nur um sie von dort wieder zurück in Register zu>> kopieren?>> weil oft memcpy eine Blackbox für den Compiler ist, dieser braucht als> Parameter ein Zeiger und diese kann nicht auf Register zeigen. Damit> zwingt man den Compiler.
Naja, soooo schwarz ist die Kiste nun auch wieder nicht. Folgenden Code
in den Ring geworfen:
Beim Zwingen muss man also schon etwas zwingender sein, etwa mit
-ffreestanding, -fno-builtin bzw. -fno-builtin-memcpy +
-fno-builtin-sin, volatile o.ä. oder Optimierung ausknipsen.
Rolf Magnus schrieb:> Oliver schrieb:>> Programmierer schrieb:>>> Wenn man auf einen Member der union etwas geschrieben hat, darf man>>> nur diesen Member wieder lesen, aber nicht die anderen! Sonst ist>>> das Ergebnis undefiniert,>>>> Ist es nicht. Es ist "implementation defined".>> Hast du einen Beleg für diese Behauptung?
C-Standard, 6.2.6.1.7:
> 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.
Okay, es ist nicht verboten die anderen Member auszulesen, aber das
Ergebnis ist unspecified, d.h. man weiß nicht was herauskommt. Ein
bestimmter Compiler für eine bestimmte Plattform könnte eine Garantie
abgeben dass immer etwas verlässliches herauskommt, aber soweit ich weiß
- und wie man sieht! - tut GCC das nicht. Um Code also wohldefiniert und
portabel zu halten, sollte man in einem union nicht auf die Werte
zugreifen, die man nicht zuvor zuletzt geschrieben hat. Alternativen
wurden oben aufgezeigt.
Programmierer schrieb:> C-Standard, 6.2.6.1.7:>> 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
Jetzt hat google doch schon einen ganz brauchbaren Überaetzter, warum
nutzt du den nicht? Der Text beschreibt was anderes.
> Okay, es ist nicht verboten die anderen Member auszulesen, aber das> Ergebnis ist unspecified, d.h. man weiß nicht was herauskommt. Ein> bestimmter Compiler für eine bestimmte Plattform könnte eine Garantie> abgeben dass immer etwas verlässliches herauskommt,
Könnte er.
Oliver
Oliver schrieb:> Jetzt hat google doch schon einen ganz brauchbaren Überaetzter, warum> nutzt du den nicht? Der Text beschreibt was anderes.
Weil manche Leute den nicht brauchen. Ich habe mir die Freiheit
genommen, aus der Vorschrift aus dem Standard eine Folgerung zu ziehen.
Wenn das zu viel auf einmal für dich war, entschuldige ich mich.
Oliver schrieb:> Könnte er.
Macht der GCC aber nicht.
Programmierer schrieb:> Rolf Magnus schrieb:>> Oliver schrieb:>>> Programmierer schrieb:>>>> Wenn man auf einen Member der union etwas geschrieben hat, darf man>>>> nur diesen Member wieder lesen, aber nicht die anderen! Sonst ist>>>> das Ergebnis undefiniert,>>>>>> Ist es nicht. Es ist "implementation defined".>>>> Hast du einen Beleg für diese Behauptung?> C-Standard, 6.2.6.1.7:>> 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.> Okay, es ist nicht verboten die anderen Member auszulesen, aber das> Ergebnis ist unspecified, d.h. man weiß nicht was herauskommt. Ein
Ein paar Ausnahmen gibt's aber schon: Eine davon: Falls die erste
Komponente aller Union-Members den gleichen (WIMRE skalaren) Typ hat.
Damit kann man dann den Inhalt der Union identifizieren:
1
union
2
{
3
intid;
4
struct{intid;data1_td1;data2_td2;...}s1;
5
struct{intid;data3_td3;data4_td4;...}s2;
6
...
7
}...
> bestimmter Compiler für eine bestimmte Plattform könnte eine Garantie> abgeben dass immer etwas verlässliches herauskommt, aber soweit ich weiß> - und wie man sieht! - tut GCC das nicht.
Doch, gerade GCC tut es :-) Und das wird auch im Kleingedruckten
zugesichert:
"Most frequently reported non-Bugs"
https://gcc.gnu.org/bugs/#nonbugs>> [...] To fix the code above, you can use a union instead of a cast>> (note that this is a GCC extension which might not work with>> other compilers) [...]
Der kleine aber feine Unterschied ist hier, ob über einen Zeiger
zugegriffen ("dereferenziert") wird oder nicht, d.h. es geht hier um
Aliasing und nicht um den Zugriff eines Union-Members über einen
anderen..
Und im Großgedruckten lautet das etwas formaler:
"C Implementation-defined behavior: Structures, unions, enumerations,
and bit-fields"
http://gcc.gnu.org/onlinedocs/gcc/Structures-unions-enumerations-and-bit-fields-implementation.html>> * A member of a union object is accessed using a member of>> a different type (C90 6.3.2.3).>>>> The relevant bytes of the representation of the object are>> treated as an object of the type used for the access.>> See Type-punning. This may be a trap representation.> Um Code also wohldefiniert und portabel zu halten,> sollte man in einem union nicht auf die Werte zugreifen,> die man nicht zuvor zuletzt geschrieben hat.> Alternativen wurden oben aufgezeigt.
Es ist eben genug Code in der Welt, der sich nicht daran hält. Siehe
Atmel. Dieses LOW8 / HIGH8 Hacks wären nen Bug-Report wert — an Atmel
wohlgemerkt, nicht an GCC.
Peter II schrieb:> für einen GCC aber etwas übertrieben.
Yep. Es gibt aber nicht nur GCC. Und es gibt GCCs, die eng in
kommerzielle IDEs eingebettet sind - bei ARM recht beliebt.
Wobei ich Entwicklungsumgebungen schon vorneweg gerne in VMs stecke,
weil sie dann Host-Migrationen völlig unbeschadet überstehen.
Es passt zwar aufgrund der Linux-Basis nicht perfekt hier rein, aber als
Beispiel taugt AVMs Router-Problem vor ein paar Monaten trotzdem ein
wenig. Die haben innerhalb einiger Tage fast die gesamte Produktpalette
aus einem Jahrzehnt gefixt, darunter Geräte, die schon viele viele Jahre
aus der Wartung waren. Um nicht in ähnliche Fallen zu laufen müssen die
eine entsprechende Infrastruktur alter Umgebungen recht konsequent
gepflegt haben - meinen Respekt dafür haben sie.
Programmierer schrieb:> Ich habe mir die Freiheit> genommen, aus der Vorschrift aus dem Standard eine Folgerung zu ziehen.
und die ist falsch.
"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."
ist übersetzt:
Wenn ein Wert in einem Mitglied eines Objektes vom Typ Union gespeichert
wird, nehmen die Bytes der Objektdarstellung, die nicht zu diesem
Mitglied gehören, undefinierte Werte an.
Der Gag des Typecasts durch eine Union ist aber gerade, daß über die
beteiligten Member auf die selben Bytes zugriffen wird.
Das "undefiniert" in dem zitierten Satz bezieht sich auf die Bytes, die
gar nicht beschrieben werden (weil z.B die Union größer ist als der
member, der zuletzt geschrieben wurde).
Daher trifft der ganze Satz hier nicht zu.
Oliver
Man muss ja nicht unbedingt eine Union nehmen, um Endianess zu prüfen.
Wenn man schon nicht über Standards wie htonl etc kommunizieren will,
gehts auch mit einem char * Pointer, der auf ein long oder short zeigt.
Casts in C erlauben eigentlich genau die Typenverwandlungen, welche
die Leute komischerweise immer mit Unions erledigen wollen. Dafür sind
Unions aber gar nicht gedacht.
Frank M. schrieb:> Casts in C erlauben eigentlich genau die Typenverwandlungen, welche> die Leute komischerweise immer mit Unions erledigen wollen.
Hm. Wie genau zerlegt man den rein über casts ein 32-Bit float in seine
4 Bytes?
Oliver
Oliver S. schrieb:> Hm. Wie genau zerlegt man den rein über casts ein 32-Bit float in seine> 4 Bytes?
Das oben genannte strict aliasing hat einige Ausnahmen. So ist es z.B.
erlaubt beliebige Pointer zu einem char* zu casten und so auf die
einzelnen Bytes zuzugreifen.
Oliver S. schrieb:> Der Gag des Typecasts durch eine Union ist aber gerade, daß über die> beteiligten Member auf die selben Bytes zugriffen wird.
Das ist *KEIN* Cast! Und die zitierte Stelle hat auch nur entfernt mit
Type-Punning zu tun.
Und das Verhalten ist abhängig von der C-Version.
Bei älteren steht dazu:
> With one exception, if the value of a member of a union object> is used when the most recent store to the object was to a> different member, the behavior is implementation-defined.
Dann wird die o.g. Ausnahme besprochen:
> One special guarantee is made in order to simplify the use> of unions: If a union contains several structures that share> a common initial sequence, and if the union object currently> contains one of these structures, it is permitted to inspect> the common initial part of any of them anywhere that a> declaration of the completed type of the union is visible.> Two structures share a common initial sequence if corresponding> members have compatible types and, for bit-fields, the same> widths for a sequence of one or more initial members.
In neueren Versionen wird Type-Punning explizit erlaubt:
> If the member used to access the contents of a union object> is not the same as the member last used to store a value in> the object, the appropriate part of the object representation> of the value is reinterpreted as an object representation in> the new type [...] a process sometimes called "type punning".
Nochmals: Der OP, bzw. den Code den er verwendet, hat ein Problem mit
Aliasing, nicht mit Type-Punning.
Johann L. schrieb:> Oliver S. schrieb:>>> Der Gag des Typecasts durch eine Union ist aber gerade, daß über die>> beteiligten Member auf die selben Bytes zugriffen wird.>> Das ist *KEIN* Cast!
ok, selbstvertändlich nicht.
> Nochmals: Der OP, bzw. den Code den er verwendet, hat ein Problem mit> Aliasing, nicht mit Type-Punning.
Ist so. Das bezog sich auf die Diskussion mit Programmierer du die
direkte union-Variante.
Oliver