Hallo Leute,
ich habe ein Projekt mit einem SCD30 CO2 Sensor umgesetzt. Es taucht
jedoch die Warnung:
"dereferencing type-punned pointer will break strict-aliasing rules
[-Wstrict-aliasing]"
auf.
Ingo Less schrieb:> Warum?
Weil das in C/C++ als undefined behaviour gilt. Der Compiler darf damit
machen, was immer er möchte, weil integer und float inkompatible
Datentypen sind.
Der tiefere Sinn dahinter ist Performance. Wenn der Compiler bei jedem
Schreibzugriff auf einen Integer damit rechnen muß, daß der auch
irgendwelche floats überschreibt, muß er immer alles neu aus dem
Speicher laden, was Zeit kostet.
Das ist die eine Hälfte. Die andere ist, daß ein Schreibzugriff auf
einen Float-Pointer natürlich dasselbe Problem mit allen anderen floats
verursacht, die der Compiler dann immer noch neu laden müßte. Das ist
blöd, wenn man Numerik-Code mit viel float (bzw. double) schreibt. Daher
gibt's seit C99 "restrict", mit dem man dem Compiler sagt, daß der
Speicherbereich, auf den ein damit gekennzeichneter Pointer verweist,
ausschließlich über diesen Pointer angesprochen wird.
Ok,
danke erstmal. Mit einer union geht es schief:
1
union{
2
uint32_tCO2_Temp;
3
floatCO2;
4
}SCD30;
kommt nicht das gleiche heraus wie bei dem Pointerkram. Oder was mache
ich da falsch? Die Endianess ist jedenfalls Big-Endian für das Ergebnis:
Zitat DB:
The CO2 concentration 400 ppm corresponds to 0x43c80000 in Big-Endian
notation
Ingo Less schrieb:> Warum? Das ist der Code aus dem Manual zum umcasten.
Kannst du diese Manual mal verlinken?
Blume schrieb:> so eine Warung kannst du durch den Einsatz einer> union> oder durch memcpy> vermeiden.
Das beseitigt die Warnung, garantiert aber nicht, dass es dann
"funktioniert". Um dazu eine Aussage zu treffen, müsste man mindestens
wissen
- welchen Typ und Wert SensorRawData hat
- auf welchem Prozessor das ganze laufen soll
- ob es sich um C oder C++ handelt
OK
> Kannst du diese Manual mal verlinken?https://cdn.sparkfun.com/assets/d/c/0/7/2/SCD30_Interface_Description.pdf> Das beseitigt die Warnung, garantiert aber nicht, dass es dann> "funktioniert". Um dazu eine Aussage zu treffen, müsste man mindestens> wissen> - welchen Typ und Wert SensorRawData hat
static uint8_t SensorRawData[NR_OF_DATABYTES];
> - auf welchem Prozessor das ganze laufen soll
STM32F0
> - ob es sich um C oder C++ handelt
C
Blume schrieb:> Ertmal toll das es leute gibt die Warnungen ernst nehmen.>> so eine Warung kannst du durch den Einsatz einer> union> oder durch memcpy> vermeiden.
Dabei ist letzterem klar der Vorzug zu geben.
Warum nicht einfach einen cast? Sehr offenkundig liefert der Sensor
(bitte nochmal im Handbuch nachsehen, dass das wirklich so ist!) einen
32-Bit-FLOAT in einem Array...
Das folgende (untested) müsste also funktionieren:
1
// Scale Values
2
uint32_tCO2_Temp=
3
((uint32_t)SensorRawData[0]<<24)+
4
((uint32_t)SensorRawData[1]<<16)+
5
((uint32_t)SensorRawData[3]<<8)+
6
((uint32_t)SensorRawData[4]);
7
8
uint32_t*CO2_TempPtr=&CO2_TempPtr;
9
float*CO2_TempFloatPtr=(float*)CO2_TempPtr;// forced cast geht immer
10
11
floatSCD30_CO2_Concentration=*CO2_TempFloatPtr;
Ich hoffe mal, dass ich mich jetzt nicht vertippt habe (habe es jetzt
nicht mit einem Compiler getestet aber so sollte es gehen).
**ABER:** Effektiv sagst du dem Compiler damit: "Halt die ****, ich weiß
es besser!" ... das sollte man nur tun, wenn man sich wirklich sicher
ist.
Hinzu kommt, dass sich die Daten in SensroRawData[] ggf. (z.B. wegen
eines IRQs?) non-atomar ändern "könnten". Das muss u.U. bedacht werden,
d.h. es muss gegebenenfalls dafür gesorgt werden, dass sich der Inhalt
des Arrays nicht von außen bedingt ändern kann, während du daraus einen
float bastelst. (Stichworte: IRQs sperren, Mutex, ... und u.U.
"volatile"...)
best
L
Nur weil du das jetzt auf zwei Zeilen verteilt hast, und den Compiler
damit austrickst, ist das trotzdem nichts anderes als oben. Der cast
erlaubt, der Zugriff über den gecasteten Pointer nicht.
Oliver
Nein... ich "trixe" den Compiler damit nicht aus... Ich sage ihm nur
explizit, was gewünscht ist.
So geht es übrigens auch (diesmal mit Compiler getestet):
1
uint32_tvalue=12345;// macht als float keinen Sinn...
2
floatfvalue=*((float*)(void*)(&value));
Der wichtige Punkt ist der Übergang über den void*... Und nein, das ist
immer noch kein Austrixen des Compilers... ;-) Und das solche Stunts
gefährlich sein können (aber nicht unbedingt müssen...), habe ich glaube
ich hinreichend deutlich gemacht.
best
L
Stefan schrieb:> Nein... ich "trixe" den Compiler damit nicht aus... Ich sage ihm> nur> explizit, was gewünscht ist.>> So geht es übrigens auch (diesmal mit Compiler getestet):uint32_t value> = 12345; // macht als float keinen Sinn...> float fvalue = *((float*)(void*)(&value));>> Der wichtige Punkt ist der Übergang über den void*... Und nein, das ist> immer noch kein Austrixen des Compilers... ;-) Und das solche Stunts> gefährlich sein können (aber nicht unbedingt müssen...), habe ich glaube> ich hinreichend deutlich gemacht.>> best> L
Das ist und bleibt undefined behavior und ist damit in C nicht erlaubt.
Was Oliver S. mit den "Compiler austricksen" meint, ist den Teil des
Compilers austricksen, der die Warnung generiert. Der Optimizer kann
dank UB immer noch machen was er will.
Und nein, du hast nichts hinreichend deutlich gemacht, außer, dass du
den C Standard nicht kennst.
Stefan schrieb:> Nein... ich "trixe" den Compiler damit nicht aus...
Doch, schon.
> Ich sage ihm nur explizit, was gewünscht ist.
Wie Oliver schon schreibt: Du sagst genau das gleiche wie im
Originalcode, nur auf zwei Zeilen verteilt. Das stellt die Warnung ab,
aber nicht das Problem, auf das sie hingewiesen hat.
> Der wichtige Punkt ist der Übergang über den void*... Und nein, das ist> immer noch kein Austrixen des Compilers... ;-)
Doch, denn das eigentliche Problem bleibt weiter bestehen: Du hast einen
Zeiger, der auf Daten eines nicht kompatiblen Typs zeigt und den du
daher nicht dereferenzieren darfst. Über welche Typen du das
zwischendurch castest, spielt dafür keine Rolle.
Ähm,... ich mach den Job schon ein paar Jahre... und nein... das ist
absolut zulässig das so zu tun. Wenn man sich vorher(!) über die
Konsequenzen Gedanken gemacht hat. Einfach "nur so" ist das in der Tat
leichtsinnig. Da sind wir uns doch einig.
Anders formuliert:
Wenn es sich um eine korrekt alignte Speicherstelle von 4 Bytes handelt,
von deren Inhalt ich absolut *sicher* weiss, dass es sich um einen
32-Bit IEEE float handelt...
Dann würde ich immer casten und nicht kopieren. U.u. würde ich selektiv
an der Stelle die Warunung abschalten. Und ja, es gäbe dafür dann auch
unit-tests. Warum? Ich habe im Regelfall keine Taktzyklen zu
verschenken.
Stefan schrieb:> Ähm,... ich mach den Job schon ein paar Jahre... und nein... das ist> absolut zulässig das so zu tun. Wenn man sich vorher(!) über die> Konsequenzen Gedanken gemacht hat. Einfach "nur so" ist das in der Tat> leichtsinnig. Da sind wir uns doch einig.
Da sind wir uns ganz siche nicht einig.
Stefan schrieb:> Anders formuliert:>> Wenn es sich um eine korrekt alignte Speicherstelle von 4 Bytes handelt,> von deren Inhalt ich absolut sicher weiss, dass es sich um einen> 32-Bit IEEE float handelt...>> Dann würde ich immer casten und nicht kopieren. U.u. würde ich selektiv> an der Stelle die Warunung abschalten. Und ja, es gäbe dafür dann auch> unit-tests. Warum? Ich habe im Regelfall keine Taktzyklen zu> verschenken.
Du hast offensichtlich nichtmal das Problem verstanden, also was genau
hier undefined behavior ist.
Und unit-tests hast du vermutlich auch nicht verstanden, aber das ist
hier irrelevant.
Ingo Less schrieb:> Vielen Dank erstmal. Könnte mir jemand von euch Profis nun eine absolut> korrekte Lösung nennen, die keinerlei Probleme erzeugen könnte?
Benutze dein CO2_Temp aus deinem ersten Beitrag* und kopiere den Inhalt
mit memcpy in den float SCD30_CO2_Concentration.
*Unter der Bedingung, dass der Inhalt von SensorRawData stimmt. Du hast
aber mit dem 400ppm Beispiel aus dem Manual ein Beispiel, mit dem du es
testen kannst. Damit kannst du also little und big endian Probleme
selbst lösen.
mh schrieb:> Benutze dein CO2_Temp aus deinem ersten Beitrag* und kopiere den Inhalt> mit memcpy in den float SCD30_CO2_Concentration.>> *Unter der Bedingung, dass der Inhalt von SensorRawData stimmt. Du hast> aber mit dem 400ppm Beispiel aus dem Manual ein Beispiel, mit dem du es> testen kannst. Damit kannst du also little und big endian Probleme> selbst lösen.
Die Lösung kann dir ebenfalls Probleme bereiten: Der einzige Unterschied
ist, dass anstelle dieselbe Speicherstelle weiterzubenutzen, *derselbe
Inhalt* nach dem memcpy an einer anderen Speicherstelle liegt. Da schon
der Inhalt "kaputt" sein kann und da auch der memcpy nicht
notwendigerweise atomar ist (ja, endianess-Probleme könnten AUCH noch
passieren...), hast du eigentlich dieselben Probleme, wie bei der
anderen Lösung am Hals. Du ersparst dir kein einziges davon.
Sorry,
L
Stefan schrieb:> Da schon> der Inhalt "kaputt" sein kann und da auch der memcpy nicht> notwendigerweise atomar ist (ja, endianess-Probleme könnten AUCH noch> passieren...), hast du eigentlich dieselben Probleme, wie bei der> anderen Lösung am Hals. Du ersparst dir kein einziges davon.
Das ist falsch. Die memcpy-Variante verstößt nicht gegen die
strict-aliasing-rule. Andere Probleme wie Endianness sind davon
unabhängig. Aber die Frage des TE bezog sich ausdrücklich auf das Thema
strict-aliasing.
Stefan schrieb:> mh schrieb:>> Benutze dein CO2_Temp aus deinem ersten Beitrag* und kopiere den Inhalt>> mit memcpy in den float SCD30_CO2_Concentration.>>>> *Unter der Bedingung, dass der Inhalt von SensorRawData stimmt. Du hast>> aber mit dem 400ppm Beispiel aus dem Manual ein Beispiel, mit dem du es>> testen kannst. Damit kannst du also little und big endian Probleme>> selbst lösen.>> Die Lösung kann dir ebenfalls Probleme bereiten: Der einzige Unterschied> ist, dass anstelle dieselbe Speicherstelle weiterzubenutzen, *derselbe> Inhalt* nach dem memcpy an einer anderen Speicherstelle liegt. Da schon> der Inhalt "kaputt" sein kann und da auch der memcpy nicht> notwendigerweise atomar ist (ja, endianess-Probleme könnten AUCH noch> passieren...), hast du eigentlich dieselben Probleme, wie bei der> anderen Lösung am Hals. Du ersparst dir kein einziges davon.>> Sorry,> L
Das sind allerdings alles Probleme, bei denen der C-Compiler ein
Programm erzeugen muss, das allen Regeln im Standard gehorchen muss.
Bei deinen Casts muss sich der Compiler nur an eine Regel halten
1
3.4.3
2
undefined behavior
3
behavior, upon use of a nonportable or erroneous program construct or of erroneous data, for which this International Standard imposes no requirements
mh schrieb:> Bei deinen Casts muss sich der Compiler nur an eine Regel halten
Eigentlich muss er sich nicht einmal daran halten - aber das wird dann
philosophisch. 🙂
Rolf M. schrieb:> mh schrieb:>> Bei deinen Casts muss sich der Compiler nur an eine Regel halten>> Eigentlich muss er sich nicht einmal daran halten - aber das wird dann> philosophisch. 🙂
😏
Stefan schrieb:> Lest die Stelle, die vorschreibt, was ein cast nach void* tun muss...> und... versteht sie... damit ist genug gesagt. Sorry, das wird mir zu> dumm.
Du meinst vermutlich den folgenden Teil?
1
6.3.2.3 Pointers
2
A pointer to void may be converted to or from a pointer to any object type. A pointer to any object type may be converted to a pointer to void and back again; the result shall compare equal to the original pointer.
Dummerweise steht da nicht, dass man durch den Pointer auf das Objekt
zugreifen darf.
Dazu muss man etwas weiter lesen
1
6.5 Expressions
2
7 An object shall have its stored value accessed only by an lvalue expression that has one of
3
the following types:88)
4
— a type compatible with the effective type of the object,
5
— a qualified version of a type compatible with the effective type of the object,
6
— a type that is the signed or unsigned type corresponding to the effective type of the object,
7
— a type that is the signed or unsigned type corresponding to a qualified version of the effective type of the object,
8
— an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union), or
9
— a character type.
Wo dein Konstrukt verboten wird, da keiner der aufgezählten Fälle
zutrifft.
Die Fußnote 88 ist übrigens
1
The intent of this list is to specify those circumstances in which an object may or may not be aliased.
Darauf bezieht sich das mehrfach erwähnte "strict aliasing". Hättest du
auch nur versucht dich darüber zu informieren, hättest du das finden
müssen.
Stefan schrieb:> Lest die Stelle, die vorschreibt, was ein cast nach void* tun muss...> und... versteht sie... damit ist genug gesagt.
Nochmal: Es geht nicht um den Cast, sondern um das Dereferenzieren eines
Zeigers auf einen inkompatiblen Typ. float und int sind inkompatibel und
bleiben das auch dann, wenn man mit dem Cast den Umweg über void* macht.
Es hat schlicht nichts miteinander zu tun. Es zeigt sich halt, dass du
immer noch nicht erfasst hast, wo überhaupt das Problem liegt.
Hier der relevante Teil:
An object shall have its stored value accessed only by an lvalue
expression that has one of the following types:
— a type compatible with the effective type of the object,
— a qualified version of a type compatible with the effective type of
the object,
— a type that is the signed or unsigned type corresponding to the
effective type of the object,
— a type that is the signed or unsigned type corresponding to a
qualified version of the effective type of the object,
— an aggregate or union type that includes one of the aforementioned
types among its members(including, recursively, a member of a
subaggregate or contained union), or
— a character type.
Und auf den Code, den du gepostet hast, trifft keiner dieser Punkte zu,
also ist das so in C nicht erlaubt. Und das ist auch der Grund für die
Warnung, die der TE bekommen hat.
Schön, wie du das alles ausgeführt hast... Dumm nur, dass dir ein
Logikfehler unterlaufen ist.
Wenn deine Ausführungen richtig wären (einfach 'mal so als
Hypothese...)... wie würde dann von Hardware, die dir nur einen Haufen
Bytes an irgendeiner Stelle im RAM gibt, z.B. per DMA und die dir diese
Stelle als void* (oder als uint8_t*, das ist dafür egal...) liefert, wie
würde dann deiner Meinung nach die korrekte, standartkonforme
Konvertierung auf die tatsächlich dort enthaltenen Daten aussehen? Mit
einem memcpy in einen entsprechend gepaddeten und alignden struct?
Wenn du da ja sagst, dann hast du vermutlich noch nie einen
Systemtreiber oder andere wirklich hardwarenahe Software von innen
gesehen...
Anders: Deine Interpretation der von dir zitieren Textpassagen ist
falsch... und für mich ist hier EOD.
L
Stefan schrieb:> Mit> einem memcpy in einen entsprechend gepaddeten und alignden struct?
Richtig. Der Compiler wird das an der Stelle übrigens ohnehin
wegoptimieren, so daß im resultierenden Binärcode kein Funktionsaufruf
mehr ist.
> Wenn du da ja sagst, dann hast du vermutlich noch nie einen> Systemtreiber oder andere wirklich hardwarenahe Software von innen> gesehen...
Schlechte Software ist kein Argument.
> Anders: Deine Interpretation der von dir zitieren Textpassagen ist> falsch...
Der C-Standard ist nicht falsch, und wenn Du nach Jahren nichts davon
weißt, ist das erschreckend.
> und für mich ist hier EOD.
Ja, EOD; weil Du keine Ahnung von C hast. Dunning-Kruger.
Stefan schrieb:> Wenn du da ja sagst, dann hast du vermutlich noch nie einen> Systemtreiber oder andere wirklich hardwarenahe Software von innen> gesehen...
Also sind Systemtreiben und hardwarenahe Software jetzt der neue C
Standard oder wie?
Stefan schrieb:> Anders: Deine Interpretation der von dir zitieren Textpassagen ist> falsch... und für mich ist hier EOD.
Dann kläre uns mal auf, wie genau die zitierten Stellen des Standards zu
interpretieren sind?
Anders: Lies den Standard und sag uns, wo genau dein Konstrukt erlaubt
wird.
Nein. Es wird aber immer dann (im negativen Sinn) extrem "witzig", wenn
mir Leute zu erklären versuchen, wie ein Standard zu interpretieren ist,
an dem ich selbst mitgewirkt habe. Insbesondere dann, wenn sie auf einen
(dicken) Logikfehler in ihrer Argumentation hingewiesen werden, den sie
immer noch nicht gefunden haben... viel Spaß noch beim Suchen... ICH
werde mich dazu nicht weiter äußern...
Hinweis an den TE: Der cast ist völlig okay, wenn man ihn so ausführt,
das der Compiler absolut(!) unmissverständlich(!) versteht: "Ey, ich bin
Gott, ich weiß was ich da tue..." das Risiko, dass dann etwas nicht wie
gewünscht funktioniert trägt man dann natürlich auch selbst... man ist
ja "Gott" mit einem solchen Statement... es dann auf den Compiler zu
schieben ist unfair...
Hint: der memcpy kann (a) überhaupt nicht "wegoptimiert" werden (think
about it...) und (b) ändert er am Sachverhalt wie die Daten an der
gegebenen Adresse zu **interpretieren** sind: NICHTS...
Wegen der Frage, wie der Standard da zu interpretieren ist:
Ein cast nach void* ist immer zulässig. Danach ist es ein pointer auf
eine Speicherstelle mit "irgendwas". Ein cast von void* auf irgendeinen
anderen Pointer-Typ ist genauso immer zulässig. Einzig beim Ergebnis der
Aktion ist man daran gebunden, dass man es besser weiß als der
Compiler... das ist (a) extrem gefährlich aber (b) genauso zulässig...
und manchmal, siehe Post des TE die einzige Option.
L
PS: "undefined" heißt auch nicht das, was hier hineininterpretiert
wurde... auch darauf habe ich schon dezent hingewiesen...
Stefan schrieb:> Nein. Es wird aber immer dann (im negativen Sinn) extrem "witzig", wenn> mir Leute zu erklären versuchen, wie ein Standard zu interpretieren ist,> an dem ich selbst mitgewirkt habe.prust
@mh: eben. wie du meinst... ich werde mir erlauben den Link auf diesen
thread auf der Arteit zu verteilen... dann ist das Prust ganz auf
meiner Seite... Hi hi hi... wenn du wüsstest...
die Warnung kommt ja weil die Optimierung "-fstrict-aliasing" gesetzt
ist. Dann kann der Compiler weitere Annahmen zur Optimierung treffen und
könnte Code wegoptimieren. Wenn der Schalter "-fstrict-aliasing" nicht
gesetzt ist, dann kommt die Warnung auch nicht.
Sicherlich ist der Code nicht portabel, aber das muss er ja nicht
unbedingt sein und allein die Verwendung von C/C++ garantiert ja auch
noch keine Portabilität.
Johannes S. schrieb:> Die Warnung kommt ja weil die Optimierung "-fstrict-aliasing" gesetzt> ist. Dann kann der Compiler weitere Annahmen zur Optimierung treffen und> könnte Code wegoptimieren. Wenn der Schalter "-fstrict-aliasing" nicht> gesetzt ist, dann kommt die Warnung auch nicht.> Sicherlich ist der Code nicht portabel, aber das muss er ja nicht> unbedingt sein und allein die Verwendung von C/C++ garantiert ja auch> noch keine Portabilität.
Danke! Ich hatte die Hoffnung schon aufgegeben...
Stefan schrieb:> Anders formuliert:>> Wenn es sich um eine korrekt alignte Speicherstelle von 4 Bytes handelt,> von deren Inhalt ich absolut sicher weiss, dass es sich um einen> 32-Bit IEEE float handelt...>> Dann würde ich immer casten und nicht kopieren. U.u. würde ich selektiv> an der Stelle die Warunung abschalten. Und ja, es gäbe dafür dann auch> unit-tests. Warum? Ich habe im Regelfall keine Taktzyklen zu> verschenken.
Wenn hier schon pedantische Standard-Diskussionen stattfinden:
Der C-Standard garantiert nur, dass floats überhaupt IEEE 754 folgen,
wenn _STDC_IEC_559 den Wert 1 hat. Eigentlich müsste man das noch
static_asserten, wenn ich mich nicht irre.
Ansonsten müsste man selbst das Binärformat in ein float konvertieren
(damit spart man sich auch den Pointer-Kram.
Johannes S. schrieb:> die Warnung kommt ja weil die Optimierung "-fstrict-aliasing" gesetzt> ist. Dann kann der Compiler weitere Annahmen zur Optimierung treffen und> könnte Code wegoptimieren. Wenn der Schalter "-fstrict-aliasing" nicht> gesetzt ist, dann kommt die Warnung auch nicht.> Sicherlich ist der Code nicht portabel, aber das muss er ja nicht> unbedingt sein und allein die Verwendung von C/C++ garantiert ja auch> noch keine Portabilität.
Man kann es aber auch einfach richtig machen.
Stefan schrieb:> Johannes S. schrieb:>> Die Warnung kommt ja weil die Optimierung "-fstrict-aliasing" gesetzt>> ist. [...]>> Danke! Ich hatte die Hoffnung schon aufgegeben...
Hast du dir überhaupt angeguckt, was "-fstrict-aliasing" genau macht? In
der gcc-Doku steht genau das, was wir dir hier schon mehrfach gesagt
haben.
Jemand schrieb:> Wenn hier schon pedantische Standard-Diskussionen stattfinden:> Der C-Standard garantiert nur, dass floats überhaupt IEEE 754 folgen,> wenn _STDC_IEC_559 den Wert 1 hat. Eigentlich müsste man das noch> static_asserten, wenn ich mich nicht irre.>> Ansonsten müsste man selbst das Binärformat in ein float konvertieren> (damit spart man sich auch den Pointer-Kram.
Auch korrekt. Das funktioniert nur, wenn man entweder genau weiß, was
der spezifische Compiler da macht oder es explizit prüft (oder
"händelt"). Portabel ist das ansonsten alles nicht. Muss es aber auch
nicht unbedingt sein. Dessen sollte man sich nur bewusst sein...
mh schrieb:> Hast du dir überhaupt angeguckt, was "-fstrict-aliasing" genau macht? In> der gcc-Doku steht genau das, was wir dir hier schon mehrfach gesagt> haben.
(A) ja ich weiß, was -fstrict-aliasing "genau" macht. Mutmaßlich besser
als du...
(B) Dein penetranter Hinweis auf memcpy entbindet dich nicht, die dort
enthaltenen Daten korrekt zu interpretieren, d.h. konkret zu wissen was
da eigentlich wie genau steht. Anders formuliert: Dein memcpy ist NICHT
hilfreich. Er frisst nur Zeit.
Faktisch machen der cast und dein memcpy exakt dasselbe. Nur mit einer
anderen Speicheraddresse. Ob das eine oder das andere
"standardkonformer" ist, ist einfach Haarespalterei, weil es am
eigentlichen Problem vorbeigeht.
Vulgo:
Fall a) "der cast":
"Compiler, interpretiere das Zeug da als float."
Fall b) "der memcpy":
"Compiler, kopiere diese Schrottbytes dort in an die Addresse des
floats."
Fällt dir was auf? Ja... Treffer... Nein... oh, immer noch daneben...
L
Ingo Less schrieb:> Ok,> danke erstmal. Mit einer union geht es schief:union{> uint32_t CO2_Temp;> float CO2;> }SCD30;> kommt nicht das gleiche heraus wie bei dem Pointerkram. Oder was mache> ich da falsch? Die Endianess ist jedenfalls Big-Endian für das Ergebnis:> Zitat DB:> The CO2 concentration 400 ppm corresponds to 0x43c80000 in Big-Endian> notation
Also, wenn du sowas machst?
Rolf M. schrieb:> Ja, wurde ja oben auch schon genannt.> Also:memcpy(&SCD30_CO2_Concentration, &CO2_Temp, sizeof> SCD30_CO2_Concentration);
Und das sollte doch eigentlich schon daran scheitern, dass
SCD30_CO2_Concentration volatile ist und der (implizite) Cast nach (void
*) das wegwirft.
Stefan schrieb:> [...]> (B) Dein penetranter Hinweis auf memcpy entbindet dich nicht, die dort> enthaltenen Daten korrekt zu interpretieren, d.h. konkret zu wissen was> da eigentlich wie genau steht. Anders formuliert: Dein memcpy ist NICHT> hilfreich. Er frisst nur Zeit.>> Faktisch machen der cast und dein memcpy exakt dasselbe. Nur mit einer> anderen Speicheraddresse. Ob das eine oder das andere> "standardkonformer" ist, ist einfach Haarespalterei, weil es am> eigentlichen Problem vorbeigeht.>> Vulgo:>> Fall a) "der cast":>> "Compiler, interpretiere das Zeug da als float.">> Fall b) "der memcpy":>> "Compiler, kopiere diese Schrottbytes dort in an die Addresse des> floats.">> Fällt dir was auf? Ja... Treffer... Nein... oh, immer noch daneben...>> L
Mir fällt das als Unterschied auf, dass bei b) im Gegensatz zu a) zu
keinem Zeitpunkt zwei Zeiger mit unterschiedlichen Typen auf die gleiche
Adresse zeigen (worum es nach meinem oberflächlichen Verständnis bei der
strict aliasing rule geht).
Dass man Annahmen über das Layout von float und uint32_t trifft, ist in
beiden Fällen der Fall. Dass das nur unter Umständen das gewünschte
Verhalten liefert, ist ja eigentlich gar nicht die ursprüngliche Frage.
Stefan schrieb:> Schön, wie du das alles ausgeführt hast... Dumm nur, dass dir ein> Logikfehler unterlaufen ist.>> Wenn deine Ausführungen richtig wären (einfach 'mal so als> Hypothese...)... wie würde dann von Hardware, die dir nur einen Haufen> Bytes an irgendeiner Stelle im RAM gibt, z.B. per DMA und die dir diese> Stelle als void* (oder als uint8_t*, das ist dafür egal...) liefert, wie> würde dann deiner Meinung nach die korrekte, standartkonforme> Konvertierung auf die tatsächlich dort enthaltenen Daten aussehen? Mit> einem memcpy in einen entsprechend gepaddeten und alignden struct?>> Wenn du da ja sagst, dann hast du vermutlich noch nie einen> Systemtreiber oder andere wirklich hardwarenahe Software von innen> gesehen...>> Anders: Deine Interpretation der von dir zitieren Textpassagen ist> falsch... und für mich ist hier EOD.>> L
Man müsste auf das Alignment der Puffer aufpassen.
Gleichzeitig gibt es in dem Fall das hier relevante Aliasing-Problem da
nicht, wenn man den Puffer in die struct casted, weil uint8_t * auf die
gleiche Addresse Zeigen darf, wie der Zeiger der Struktur. Wäre der
„Haufen Bytes“ vom Typ uint32_t * wäre das nicht mehr gegeben. So
zumindest mein Verständnis.
Ob der „Haufen Bytes“ mit der Struct kompatibel ist, wie sich der
Programmierer das denkt, ist dann das nächste Problem.
Rolf M. schrieb:> Blume schrieb:>> Ertmal toll das es leute gibt die Warnungen ernst nehmen.>>>> so eine Warung kannst du durch den Einsatz einer>> union>> oder durch memcpy>> vermeiden.>> Dabei ist letzterem klar der Vorzug zu geben.
Und wieso das, wenn ich fragen darf?
Bei memcpy muss man doch erstmal davon ausgehen, dass es (ohne
Optimierung) byteweise kopiert, bei der union nicht. Und es erscheint
mir etwas lesbarer.
Die union wird hier
https://gcc.gnu.org/onlinedocs/gcc-4.9.1/gcc/Optimize-Options.html ja
sogar als Positivbeispiel genannt.
Stefan schrieb:> Anders formuliert:>> Wenn es sich um eine korrekt alignte Speicherstelle von 4 Bytes handelt,> von deren Inhalt ich absolut sicher weiss, dass es sich um einen> 32-Bit IEEE float handelt...>> Dann würde ich immer casten und nicht kopieren. U.u. würde ich selektiv> an der Stelle die Warunung abschalten. Und ja, es gäbe dafür dann auch> unit-tests. Warum? Ich habe im Regelfall keine Taktzyklen zu> verschenken.
Und ihr redet mit einer unnötig aufgeheizten Rhetorik aneinander vorbei.
Stefan, du meinst ein anderes Problem, als die anderen, wenn ich dich
richtig verstehe.
Nein, es geht bei der strict-aliasing-rule darum, dass so etwas nicht
einfach so "aus Versehen", sondern nur in absoluter, voller und
ausdrücklicher Absicht passieren kann. Denn in fast allen Fällen handelt
es sich hierbei um einen unbeabsichtigten Fehler.
Es gibt aber auch Fälle, wo genau dies die gewünschte Absicht darstellt.
Genau dafür ist der Umweg über den void* offen auch dann, wenn
strict-aliasing an ist...
Alle Warnungen, die in diesem Thread schon durchgekaut wurden ("Achtung,
Sie sind auf sich gestellt und betreten ein echt ekeliges Minenfeld.
Seien Sie bitte sehr, sehr vorsichtig...") greifen natürlich...
Jemand schrieb:> Und ihr redet mit einer unnötig aufgeheizten Rhetorik aneinander vorbei.> Stefan, du meinst ein anderes Problem, als die anderen, wenn ich dich> richtig verstehe.
Jain...
Das mit der aufgeheizten Rhetorik unterstreiche ich aber. Ist auch mir
zu heiß so...
Jemand schrieb:> Rolf M. schrieb:>> Ja, wurde ja oben auch schon genannt.>> Also:memcpy(&SCD30_CO2_Concentration, &CO2_Temp, sizeof>> SCD30_CO2_Concentration);>> Und das sollte doch eigentlich schon daran scheitern, dass> SCD30_CO2_Concentration volatile ist und der (implizite) Cast nach (void> *) das wegwirft.
Da hast du recht, ohne volatile darf man nicht drauf zugreifen. Da man
die Konvertierung eh in eine Funktion auslagert ist das zum Glück kein
Problem
1
inlinefloatconvert(uint32_tval){
2
floattmp;
3
memcpy(&tmp,&val,4);
4
returntmp;
5
}
6
SCD30_CO2_Concentration=convert(CO2_Temp);
Da macht der Compiler dann eine 4 Byte move draus. Genauso für
1
floatconvert(uint32_tval){
2
union{
3
uint32_tu;
4
floatf;
5
}Temp;
6
7
Temp.u=val;
8
returnTemp.f;
9
}
Alternativ kann man auch gleich die ganze Sensor-char-Array nach
CO2-float in einer Funktion zusammenfassen mit
1
floatconvert(uint8_t*src){
2
floattmp;
3
uint8_t*p=&tmp;
4
5
p[0]=src[0];
6
p[1]=src[1];
7
p[2]=src[3];
8
p[3]=src[4];
9
10
returntmp;
11
}
(Wenn man es entsprechend der byte order anpasst ...)
Stefan schrieb:> Nein, es geht bei der strict-aliasing-rule darum, dass so etwas> nicht> einfach so "aus Versehen", sondern nur in absoluter, voller und> ausdrücklicher Absicht passieren kann. Denn in fast allen Fällen handelt> es sich hierbei um einen unbeabsichtigten Fehler.>> Es gibt aber auch Fälle, wo genau dies die gewünschte Absicht darstellt.> Genau dafür ist der Umweg über den void* offen auch dann, wenn> strict-aliasing an ist...>> Alle Warnungen, die in diesem Thread schon durchgekaut wurden ("Achtung,> Sie sind auf sich gestellt und betreten ein echt ekeliges Minenfeld.> Seien Sie bitte sehr, sehr vorsichtig...") greifen natürlich...
Das magst du glauben, ist aber nicht so. Wenn du uns überzeugen willst,
musst du mal ein paar nachweisbare Fakten vorlegen. Woher weißt du z.B.,
was die Absicht hinter der strict-aliasing Regel ist?
Momentan läuft diese Diskussion nach dem Schema:
Wir sagen: "Der Standard sagt X".
Stefan sagt: "Der Standard mag X sagen, gemeint ist aber Y".
Stefan schrieb:> Nein, es geht bei der strict-aliasing-rule darum, dass so etwas> nicht> einfach so "aus Versehen", sondern nur in absoluter, voller und> ausdrücklicher Absicht passieren kann. Denn in fast allen Fällen handelt> es sich hierbei um einen unbeabsichtigten Fehler.>> Es gibt aber auch Fälle, wo genau dies die gewünschte Absicht darstellt.> Genau dafür ist der Umweg über den void* offen auch dann, wenn> strict-aliasing an ist...>> Alle Warnungen, die in diesem Thread schon durchgekaut wurden ("Achtung,> Sie sind auf sich gestellt und betreten ein echt ekeliges Minenfeld.> Seien Sie bitte sehr, sehr vorsichtig...") greifen natürlich...
Nach meinem Verständnis existiert die strict aliasing rule aus folgendem
Grund:
1
float32_tf(uint32_t*a,float32_t*b){
2
// hierfür wird b aus dem Speicher in ein Register geladen
3
float32_tc=*b;
4
*a=42;
5
// für den folgenden Code muss der Wert von b nun neu aus
6
// dem Speicher geladen werden, falls a und b sich überlappen.
7
// Wenn die strict aliasing rule nicht existieren würde, müsste
8
// der Compiler genau dies machen, weil er hier keine Möglichkeit
9
// hat, das auszuschließen. Das ist aber ineffizient.
10
// Wenn man aber verbietet, dass Zeigertypen verschiedener
11
// Datentypen (etwas komplizierter als das) auf den gleichen
12
// Speicher zeigen, kann der Compiler annehmen, dass die
13
// Speicherbereiche von a und b sich nicht überschneiden und
14
// darauf verzichten, *b neu aus dem Speicher in ein Register
15
// zu laden. Und -fstrict-aliasing sagt dem Compiler, dass er
16
// genau das tun darf.
17
c+=*b;
18
returnc;
19
}
In dem Beispiel spielt keine Rolle, ob der aufrufende Code von f diese
Konvertierung über (void *)-Umwege gemacht hat, das kann der Compiler zB
in einer Library-Funktion nicht einmal wissen.
Wären a und b in dem Beispiel beide vom selben Typ, muss der Compiler
berücksichtigen, dass sie sich überschneiden könnten und kann die
Optimierung nicht machen. Und restrict ist dann ein Weg, ihm das doch zu
ermöglichen.
Korrigiert mich gerne, ich lerne gerade dazu (danke an den TE für die
Gelegenheit). Und restrict habe ich gerade erst wirklich verstanden,
denke ich :) .
Jemand schrieb:> Nach meinem Verständnis existiert die strict aliasing rule aus folgendem Grund> [...]
Ob das der Grund ist, warum die Regel existiert kann ich nicht sagen,
der beschriebene Vorteil und die Wirkung von restrict stimmt soweit.
Ansonsten ist die logik bei fstrict-aliasing etwas anders als du
beschrieben hast:
Jemand schrieb:> Und -fstrict-aliasing sagt dem Compiler, dass er> // genau das tun darf.
fno-strict-aliasing sagt dem Compiler, dass er die strict aliasing Regel
aus dem Standard ignorieren soll und nicht für Optimierungszwecke
benutzen darf.
fstrict-aliasing sagt dem Compiler, dass er sich an den Standard halten
soll und dementspechend optimieren darf wie er will.
Stefan schrieb:> ja, so kann man das auch erklären.> find ich besser als meine Erklärung...
Deine Erklärungen waren ja bis jetzt auch nachweislich falsch ;-)
mh schrieb:> Da hast du recht, ohne volatile darf man nicht drauf zugreifen. Da man> die Konvertierung eh in eine Funktion auslagert ist das zum Glück kein> Probleminline float convert(uint32_t val) {> float tmp;> memcpy(&tmp, &val, 4);> return tmp;> }> SCD30_CO2_Concentration = convert(CO2_Temp);> Da macht der Compiler dann eine 4 Byte move draus
Wobei das einiges an Optimierungsarbeit voraussetzt, oder?
Der Compiler müsste ja erstmal memcpy inlinen, dann feststellen, dass
das Alignment von tmp so passt, dass er 4 Byte auf einmal lesen kann,
dann feststellen, dass er tmp wegoptimieren kann (was er auch nur kann,
wenn er 4 Byte auf einmal kopiert, da volatile). Dem gcc traue ich das
auf jeden Fall zu, es trifft nur mehr Anforderungen an den Compiler, um
an den gleichen Maschinencode zu kommen, als die enum-Variante, deswegen
interessiert mich, was für die Variante spricht :)
mh schrieb:> Jemand schrieb:>> Nach meinem Verständnis existiert die strict aliasing rule aus folgendem Grund>> [...]> Ob das der Grund ist, warum die Regel existiert kann ich nicht sagen,> der beschriebene Vorteil und die Wirkung von restrict stimmt soweit.> Ansonsten ist die logik bei fstrict-aliasing etwas anders als du> beschrieben hast:> Jemand schrieb:>> Und -fstrict-aliasing sagt dem Compiler, dass er>> // genau das tun darf.> fno-strict-aliasing sagt dem Compiler, dass er die strict aliasing Regel> aus dem Standard ignorieren soll und nicht für Optimierungszwecke> benutzen darf.> fstrict-aliasing sagt dem Compiler, dass er sich an den Standard halten> soll und dementspechend optimieren darf wie er will.
Stimmt, danke für die Hinweise.
mh schrieb:> float tmp;> uint8_t *p = &tmp;
nope. Das gibt sofort Mecker. Ausser man setzt einen typecast vor
&tmp...
Aber hier die union hin und das wäre eine saubere Lösung.
mh schrieb:> Deine Erklärungen waren ja bis jetzt auch nachweislich falsch ;-)
Ob Smiley oder nicht. Jetzt reicht es langsam. Beweise endlich deine
kruden Behauptungen über die Unzulässigkeit des generellen casts
von/nach void*. Was du bisher aus dem Zusammenhang gerissen aus dem
Standard zusammenfabuliert hast ist gelinde gesagt ein Witz.
mh schrieb:> Da hast du recht, ohne volatile darf man nicht drauf zugreifen. Da man> die Konvertierung eh in eine Funktion auslagert ist das zum Glück kein> Probleminline float convert(uint32_t val) {> float tmp;> memcpy(&tmp, &val, 4);> return tmp;> }> SCD30_CO2_Concentration = convert(CO2_Temp);> Da macht der Compiler dann eine 4 Byte move draus. Genauso fürfloat> convert(uint32_t val) {> union {> uint32_t u;> float f;> } Temp;> Temp.u = val;> return Temp.f;> }>> Alternativ kann man auch gleich die ganze Sensor-char-Array nach> CO2-float in einer Funktion zusammenfassen mitfloat convert(uint8_t> *src) {> float tmp;> uint8_t *p = &tmp;> p[0] = src[0];> p[1] = src[1];> p[2] = src[3];> p[3] = src[4];> return tmp;> }> (Wenn man es entsprechend der byte order anpasst ...)
Und ich habe gerade, nur weil es mich mal interessiert, alle drei
Varianten enebeneinander kompiliert (ohne inline):
1
#include<stdint.h>
2
#include<string.h>
3
4
floata(uint32_tval){
5
floattmp;
6
memcpy(&tmp,&val,4);
7
returntmp;
8
}
9
10
floatb(uint32_tval){
11
union{
12
uint32_tu;
13
floatf;
14
}Temp;
15
Temp.u=val;
16
returnTemp.f;
17
}
18
19
floatc(uint8_t*src){
20
floattmp;
21
uint8_t*p=&tmp;
22
p[0]=src[0];
23
p[1]=src[1];
24
p[2]=src[3];
25
p[3]=src[4];
26
returntmp;
27
}
Bei -O0 ergeben alle drei Varianten unterschiedlichen Code (ich bin
nicht fit genug in Assembler, um genau zu wissen, was da passiert).
Bei -O1, -O2 und selbst -O3 ergeben a und b exakt den gleichen Code,
scheinbar 4 Byte Kopie, Variante c scheint bei einem byteweisen Kopieren
zu bleiben, selbst bei -O3.
GCC 10.2.0
Jemand schrieb:> Wobei das einiges an Optimierungsarbeit voraussetzt, oder?>> Der Compiler müsste ja erstmal memcpy inlinen, dann feststellen, dass> das Alignment von tmp so passt, dass er 4 Byte auf einmal lesen kann,> dann feststellen, dass er tmp wegoptimieren kann (was er auch nur kann,> wenn er 4 Byte auf einmal kopiert, da volatile). Dem gcc traue ich das> auf jeden Fall zu, es trifft nur mehr Anforderungen an den Compiler, um> an den gleichen Maschinencode zu kommen, als die enum-Variante, deswegen> interessiert mich, was für die Variante spricht :)
Das trifft auf die Union-Variante aber alles genauso zu, oder? In der
convert Funktion ist übrigens nichts volatile.
Ansonsten spricht für die memcpy-Variante, dass sie auch in C++
funktioniert ;-)
Johannes S. schrieb:> mh schrieb:>> float tmp;>> uint8_t *p = &tmp;>> nope. Das gibt sofort Mecker. Ausser man setzt einen typecast vor> &tmp...> Aber hier die union hin und das wäre eine saubere Lösung.
Ja da sollte man einen expliziten cast einfügen, der beseitigt dann aber
nur die Warnung. Es ist auch ohne cast korrekt.
Jemand schrieb:> Bei -O0 ergeben alle drei Varianten unterschiedlichen Code (ich bin> nicht fit genug in Assembler, um genau zu wissen, was da passiert).> Bei -O1, -O2 und selbst -O3 ergeben a und b exakt den gleichen Code,> scheinbar 4 Byte Kopie, Variante c scheint bei einem byteweisen Kopieren> zu bleiben, selbst bei -O3.> GCC 10.2.0
Die Variante 3 macht ja auch etwas anderes. Die nimmt die 5 Bytes vom
Sensor entgegen und ignoriert dann den crc-Wert bei Index 2. Das muss
man bei den ersten beiden noch zusätztlich machen. Mit der Variante ist
man die ganze Schiftorgie aus dem ursprünglichen Post los.
bei c) sind die src Bytes ja nicht aufeinanderfolgend, das müsste man
sonst vorher beim Setzen des uint32_t machen.
Und der Compiler hat da beide Augen zugedrückt beim Zuweisen des anderen
Pointertyps.
Johannes S. schrieb:> Und der Compiler hat da beide Augen zugedrückt beim Zuweisen des anderen> Pointertyps.
Das ist explizit erlaubt. Oder darf man in C an der Stelle keinen
uint8_t, sondern muss nen char nehmen? Aber da es keinen speziellen
Grund für den uint8_t gibt sollte man auf Nummer sicher gehen und nen
char benutzen.
ok, ich hatte das in ein C++ Projekt gehackt und das ist (zum Glück)
penibler.
Aber dann hat man genau das was der TO zu Anfang in einer Zeile
geschrieben hat, pointer auf anderen Typ casten und gleich wieder
dereferenzieren.
Johannes S. schrieb:> ok, ich hatte das in ein C++ Projekt gehackt und das ist (zum Glück)> penibler.
In C++ ist das ganze zum Glück nicht mehr nötig dank std::bit_cast ;-)
Johannes S. schrieb:> Aber dann hat man genau das was der TO zu Anfang in einer Zeile> geschrieben hat, pointer auf anderen Typ casten und gleich wieder> dereferenzieren.
Ja, aber mit einem char, der explizit in der strict aliasing Regel
erlaubt ist
1
6.5 Expressions
2
7 An object shall have its stored value accessed only by an lvalue expression that has one of
3
the following types:88)
4
— a type compatible with the effective type of the object,
5
— a qualified version of a type compatible with the effective type of the object,
6
— a type that is the signed or unsigned type corresponding to the effective type of the object,
7
— a type that is the signed or unsigned type corresponding to a qualified version of the effective type of the object,
8
— an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union), or
ok, damit gibts dann auch keine type-punned warning.
mh schrieb:> In C++ ist das ganze zum Glück nicht mehr nötig dank std::bit_cast ;-)
so modern bin ich noch nicht. Benutze allerdings auch gerade ein C++17
Feature mit C++14 Einstellung, der gcc warnt aber generiert den Code
trotzdem.
Johannes S. schrieb:> ok, damit gibts dann auch keine type-punned warning.>> mh schrieb:>> In C++ ist das ganze zum Glück nicht mehr nötig dank std::bit_cast ;-)>> so modern bin ich noch nicht. Benutze allerdings auch gerade ein C++17> Feature mit C++14 Einstellung, der gcc warnt aber generiert den Code> trotzdem.
Du kannst dir bit_cast selbst schreiben siehe das Notes Kapitel auf
https://en.cppreference.com/w/cpp/numeric/bit_cast
Benutzt die memcpy-Variante von oben, wie passend ;-)
Stefan schrieb:> Genau dafür ist der Umweg über den void* offen auch dann, wenn> strict-aliasing an ist...
Quatsch. Und nein, Du hast nicht am C-Standard mitgewirkt. Du begreifst
ja nicht einmal das Problem.
Du kannst zwar nach void* casten, aber das bedeutet nicht, daß Du dann
nach re-cast dereferenzieren darfst. Das ist auch offensichtlich in
Fällen, wo der re-cast zu einem Datentyp mit größerem Alignment führen
würde.
Johannes S. schrieb:> die Warnung kommt ja weil die Optimierung "-fstrict-aliasing" gesetzt> ist.
Das ist der Default des C-Standards. Dafür braucht es keinen Schalter.
Jemand schrieb:> Und wieso das, wenn ich fragen darf?>> Bei memcpy muss man doch erstmal davon ausgehen, dass es (ohne> Optimierung) byteweise kopiert
Nein, bei einer statischen Länge von vier Bytes und obendrien passendem
alignment kopiert memcpy nicht byteweise, davon kannst Du ausgehen.
Der Hauptvorteil von memcpy besteht darin, daß man den Code auch als C++
compilieren kann, was bei union type punning nicht erlaubt wäre.
Letzteres funktioniert zwar bei GCC auch als C++, aber da das nicht vom
Sprachstandard garantiert ist, kann sich das jederzeit ändern.
Stefan schrieb:> kruden Behauptungen über die Unzulässigkeit des generellen casts> von/nach void*
Niemand behauptet das.
Du darfst so viel Pointer casten, wie du möchtest.
Es geht lediglich um das Dereferenzieren deiner Pointerorgie.
Jemand schrieb:> Rolf M. schrieb:>>> so eine Warung kannst du durch den Einsatz einer>>> union>>> oder durch memcpy>>> vermeiden.>>>> Dabei ist letzterem klar der Vorzug zu geben.>> Und wieso das, wenn ich fragen darf?
Weil unions im Gegensatz zu memcpy für sowas nicht gedacht sind.
Ursprünglich war das mit der union in C auch verboten und wurde
nachträglich erlaubt, weil es so viel eingesetzt wrude. In C++ ist es
bis heute verboten.
> Bei memcpy muss man doch erstmal davon ausgehen, dass es (ohne> Optimierung) byteweise kopiert, bei der union nicht.
Warum bei der union nicht? Bei der union muss ich ohne angenommene
Optimierung sogar zweimal kopieren, einmal in die union rein und dann
wieder raus.
> Und es erscheint mir etwas lesbarer.
Ich finde memcpy lesbarer, und dazu weniger umständlich. Bei der union
muss ich mir für die Konvertierung extra einen neuen Datentyp anlegen
und dann den Wert da rein und wieder raus kopieren. Bei memcpy ist das
mit dem einen Aufruf erledigt.
> Die union wird hier> https://gcc.gnu.org/onlinedocs/gcc-4.9.1/gcc/Optimize-Options.html ja> sogar als Positivbeispiel genannt.
Ich sehe da unions nur generell als Beispiel, aber nirgends, dass man
sie gegenüber Alternativen bevorzugen soll.
Jemand schrieb:> Der Compiler müsste ja erstmal memcpy inlinen, dann feststellen, dass> das Alignment von tmp so passt, dass er 4 Byte auf einmal lesen kann,> dann feststellen, dass er tmp wegoptimieren kann (was er auch nur kann,> wenn er 4 Byte auf einmal kopiert, da volatile).
Der gcc geht da etwas anders vor. Er macht da im ersten Schritt schon
gar keinen Funktionsaufruf draus. Bei dem ist "einfach" ein Großteil der
Standardfunktionen bereits im Compiler selbst umgesetzt (*). memcpy()
gehört dazu. Er sieht bei einem Aufruf, dass der Wert kopiert werden
soll und macht dann an der jeweiligen Stelle das optimale draus. Bei der
union macht er das auch, daher kommt am Ende der selbe Code raus.
* Hier findet man eine Liste:
https://gcc.gnu.org/onlinedocs/gcc-10.2.0/gcc/Other-Builtins.html#Other-BuiltinsStefan schrieb:> Ob Smiley oder nicht. Jetzt reicht es langsam. Beweise endlich deine> kruden Behauptungen über die Unzulässigkeit des generellen casts> von/nach void*.
Moment mal. Du hast die Behauptung aufgestellt, dass ein Cast über void*
das Problem beseitigen würde, da musst du das auch erst mal belegen. Ich
hatte oben zitiert, gegen welche Regel dein Code meiner Ansicht nach
verstößt. Ich hatte auch erklärt, warum der Cast nichts an der Situation
ändert. Daraufhin bist du dann patzig geworden:
Stefan schrieb:> Lest die Stelle, die vorschreibt, was ein cast nach void* tun muss...> und... versteht sie... damit ist genug gesagt. Sorry, das wird mir zu> dumm.
Und:
Stefan schrieb:> Schön, wie du das alles ausgeführt hast... Dumm nur, dass dir ein> Logikfehler unterlaufen ist.
Leider erklärst du nicht, was für einen Logikfehler du meinst. Oder das:
Stefan schrieb:> Insbesondere dann, wenn sie auf einen (dicken) Logikfehler in ihrer> Argumentation hingewiesen werden, den sie immer noch nicht gefunden haben...> viel Spaß noch beim Suchen... ICH werde mich dazu nicht weiter äußern...
Aber sich dann über die aufgeheizte Retorik beschweren …
Während wir hier also den Standard zitieren und unseren Standpunkt
ausführlich erklären, wirfst du nur irgendwelche unbelegten Behauptungen
in den Raum.
mh schrieb:> Jemand schrieb:>> [...]>> Das trifft auf die Union-Variante aber alles genauso zu, oder? In der> convert Funktion ist übrigens nichts volatile.>> Ansonsten spricht für die memcpy-Variante, dass sie auch in C++> funktioniert ;-)
Okay, das ist natürlich ein guter Grund, danke für den Hinweis (auch an
die, die darauf weiter unten hingewiesen haben). Das hat mich überzeugt
:) .
Bei der Union-Variante wäre das Aligment ja „direkt“ garantiert und es
gäbe keinen Funktionsaufruf, aber egal, ab -O1 scheint es ja zu
funktionieren.
> Johannes S. schrieb:>> mh schrieb:>>> float tmp;>>> uint8_t *p = &tmp;>>>> nope. Das gibt sofort Mecker. Ausser man setzt einen typecast vor>> &tmp...>> Aber hier die union hin und das wäre eine saubere Lösung.> Ja da sollte man einen expliziten cast einfügen, der beseitigt dann aber> nur die Warnung. Es ist auch ohne cast korrekt.>> Jemand schrieb:>> [...]>> Die Variante 3 macht ja auch etwas anderes. Die nimmt die 5 Bytes vom> Sensor entgegen und ignoriert dann den crc-Wert bei Index 2. Das muss> man bei den ersten beiden noch zusätztlich machen. Mit der Variante ist> man die ganze Schiftorgie aus dem ursprünglichen Post los.
Das hatte ich gestern Nacht übersehen.
Variante c so abzuändern, dass es die ersten 4 Byte sind, ändert
interessanterweise nichts daran. Wenn ich so darüber nachdenke, dürfte
es daran liegen, dass der Compiler bei einem uint8_t *-Parameter die
Aussagen zum Alignment nicht treffen kann.
Hallo
Nop schrieb:> Weil das in C/C++ als undefined behaviour gilt. Der Compiler darf damit> machen, was immer er möchte, weil integer und float inkompatible> Datentypen sind.>> Der tiefere Sinn dahinter ist Performance. Wenn der Compiler bei jedem> Schreibzugriff auf einen Integer damit rechnen muß,...
Warum sehen Fehlermeldung bzw. Warnungen nicht so (vieleicht noch ein
klein wenig "runder" formuliert) aus?
Wenigsten in der Form: Fehlermeldung, Warnung -->Dann so etwas wie "Get
more info" und dann --> halt so eine Erläuterung.
Im Hintergrund befindet sich selbstverständlich "einfach" ein aktuelles
Sprachpaket - weil viele Englisch halt nicht perfekt beherschen was
besonders unschön ist wennn man zusätzlich auch C nicht perfekt
beherscht.
Am besten noch ein wenig abhängig was ("Computer" oder uC) gerade
programmiert wird bzw. mal geflasht werden soll.
Vom Prinzip müsste das generell bei Fehlkermeldungen bei Dianosegeräten
und deren Software sein.
(Gruß an die "Hersteller" von Fehler- Diagnosemeldungen aller Art so
sein:Ananstatt IUnput 4711 err. oder gar Fehler C099 lieber - "Oft ist
der Sensor 4711 der vorne rechts mittels des gelben Kabel angeschlossen
ist und wie eine Möhre aussieht, verschmutzt oder der Stecker hat sich
nicht sichtbar gelockert. Seltener ist auch mal..."
Man darf ja mal träumen dürfen... :-)
Praktiker
Jemand schrieb:> Bei der Union-Variante wäre das Aligment ja „direkt“ garantiert
Ist das so? Ich konnte auf die Schnelle nur folgendes finden:
1
6.7.2.1 Structure and union specifiers
2
[...]
3
14 Each non-bit-field member of a structure or union object is aligned in an implementation-defined manner appropriate to its type.
Und Punkt 15 gilt explizit nur für strukturen, nicht Unions:
1
15 Within a structure object, the non-bit-field members and the units in which bit-fields reside have addresses that increase in the order in which they are declared. A pointer to a structure object, suitably converted, points to its initial member (or if that member is a bit-field, then to the unit in which it resides), and vice versa. There may be unnamed padding within a structure object, but not at its beginning.
Jemand schrieb:> Variante c so abzuändern, dass es die ersten 4 Byte sind, ändert> interessanterweise nichts daran. Wenn ich so darüber nachdenke, dürfte> es daran liegen, dass der Compiler bei einem uint8_t *-Parameter die> Aussagen zum Alignment nicht treffen kann.
Das kommt drauf an .... "ARM gcc trunk (linux)" macht daraus
1
convert3:
2
ldr r3, [r0] @ unaligned
3
vmov s0, r3
4
bx lr
Gibt sogar nen netten Hinweis, warum das ldr dazugekommen ist.
Das ist definitiv eine Verletzung des ISO-C-Standards (Abschnitt 6.5,
Absatz 7, auch als "Strict Aliasing Rule" bekannt). Daran ändert auch
der von Stefan vorgeschlagene Umweg über einen void-Pointer nichts.
Der Grund dafür: CO2_Temp ist als uint32_t deklariert, weswegen der
effektive Typ des von CO2_Temp referenzierten Objekts ebenfalls uint32
ist. Da der effektive Typ von einer Deklaration stammt, kann er weder
geändert (wie im Beispiel durch ✶(float✶)) noch gelöscht und dann wieder
neudefiniert (wie in Stefans Vorschlag durch ✶(float✶)(void✶)) werden.
Die beliebige Festlegung des effektiven Typs eines bereits existierenden
Objekts ist nur möglich, wenn dieses keinen deklarierten Typ hat. Das
ist bspw. bei dynamisch mit malloc erzeugten Objekten der Fall.
Dass das Ganze dennoch funktioniert (und das sogar unabhängig vom
verwendeten Compiler und der Optimierungsstufe), liegt einfach daran,
dass die Strict Aliasing Rule hier keinerlei Optimierung ermöglicht, die
nicht auch ohne diese Regel möglich wäre. Trotzdem ist und bleibt die
obige Zuweisung UB, und ein böswilliger Compiler (den es zum Glück nicht
gibt) könnte daraus – ohne dabei gegen den C-Standard zu verstoßen –
Code erzeugen, der die Festplatte formatiert
Man sollte sich solche Konstrukte allein schon deswegen abgewöhnen, weil
man sie sonst irgendwann versehentlich auch in einem Kontext nutzt, wo
die Optimierung eben doch zuschlägt und damit zu einem fehlerhaften
Verhalten des Programms führt.
Hier ist noch ein kleines Beispiel (ähnlich dem von "Jemand" bereits
geposteten), das die Problematik des Zugriffs auf Objekte mit einem
unzulässigen L-Value-Typ sowie die Sinnlosigkeit des void✶-Umwegs
aufzteigt:
1
#include<stdio.h>
2
#include<stdint.h>
3
4
floatfoo(uint32_t*pu,float*pf){
5
*pf=1.0;
6
*pu=0x40000000;
7
return*pf;
8
}
9
10
intmain(void){
11
uint32_tu;
12
13
// Alternative 1: böse
14
15
printf("Alternative 1: %f\n",foo(&u,(float*)&u));
16
17
// Alternative 2: genauso böse
18
19
void*pv=(void*)&u;
20
float*pf=(float*)pv;
21
printf("Alternative 2: %f\n",foo(&u,pf));
22
}
1
$ gcc -O1 -o test test.c
2
$ test
3
Alternative 1: 2.000000
4
Alternative 2: 2.000000
5
$ gcc -O2 -o test test.c
6
$ test
7
Alternative 1: 1.000000
8
Alternative 2: 1.000000
Das ausgegebene Ergebnis ist also abhängig von der Optimierungsstufe
(ganz schlecht), und der void✶-Umweg ändert daran überhaupt nichts.
PS: Ich habe im Fließtext das '*' überall durch '✶' ersetzt, um eine
Fehlinterpretation durch die Forensoftware zu vermeiden.
Praktiker schrieb:> Warum sehen Fehlermeldung bzw. Warnungen nicht so (vieleicht noch ein> klein wenig "runder" formuliert) aus?
Wieso, die Fehlermeldung verweist doch schon auf strict aliasing. Das
ist doch klar.
> Im Hintergrund befindet sich selbstverständlich "einfach" ein aktuelles> Sprachpaket - weil viele Englisch halt nicht perfekt beherschen
Ich meine mich zu erinnern, daß GCC bei entsprechender Spracheinstellung
tatsächlich deutsche Meldungen ausgibt - was einer der Gründe ist, wieso
ich mein System auf englisch eingestellt habe.
> was besonders unschön ist wennn man zusätzlich auch C nicht perfekt> beherscht.
Wenn man eine Fehlermeldung nicht versteht, tippt man sie bei Google
ein. "Früher"(tm) mußte man ja tatsächlich noch im Compiler-Handbuch
rumblättern. Erfahrene Entwickler wollen außerdem nicht vom Compiler mit
redundanten Spracheinführungskursen zugelabert werden.
Ärgerlicher ist eher, wenn Compiler zuviel ausgeben, weil ein Tippfehler
nicht nur angemeckert wird, sondern auch noch zu Folgefehlermeldungen
führt. Legendär war das früher mit C++, wo wenig hilfreiche
Fehlermeldungen länger als das ganze Programm sein konnten, wenn
irgendwo tief in einem Template was falsch hing.
> Am besten noch ein wenig abhängig was ("Computer" oder uC) gerade> programmiert wird bzw. mal geflasht werden soll.
Das wäre zumindest im vorliegenden Fall nicht sinnvoll, weil das
zugrundeliegende Problem überall dasselbe wäre.
mh schrieb:> Jemand schrieb:>> Bei der Union-Variante wäre das Aligment ja „direkt“ garantiert> Ist das so? Ich konnte auf die Schnelle nur folgendes finden:6.7.2.1> Structure and union specifiers> [...]> 14 Each non-bit-field member of a structure or union object is aligned> in an implementation-defined manner appropriate to its type.> Und Punkt 15 gilt explizit nur für strukturen, nicht Unions:15 Within a> structure object, the non-bit-field members and the units in which> bit-fields reside have addresses that increase in the order in which> they are declared. A pointer to a structure object, suitably converted,> points to its initial member (or if that member is a bit-field, then to> the unit in which it resides), and vice versa. There may be unnamed> padding within a structure object, but not at its beginning.
Okay, ich gebe zu, nicht auf Rechnung gehabt zu haben, dass unions auch
gepadded sein könnten :)
Was die Fehlermeldungssache angeht: Gute Fehlermeldungen sind schwer.
Bei C/C++ vermutlich insbesondere (spätestens bei Templates ^^). Und
Fehlermeldungen weit über den Punkt hinaus zu optimieren (und
sicherzustellen, dass sie wirklich immer die richtigen Hinweise geben),
ab dem jemand mit etwas Erfahrung oder der Zeit eine Suchmaschine zu
benutzen / im Standard oder einer Referenz nachzublättern, ist
vermutlich Abwägungssache.
Außerdem haben kurze Fehlermeldungen imo eindeutig auch Vorteile. Selbst
ein „mehr anzeigen“ (was im Terminal eh nicht so ohne weiteres geht),
würde mich da teilweise nerven. Irgendein Compiler, den ich mal benutzt
habe, hatte einen explain-Parameter, mit dem man sich ausführlichere
Infos zu einer Fehlermeldungsart ausgeben lassen konnte.
Nop schrieb:>> Im Hintergrund befindet sich selbstverständlich "einfach" ein aktuelles>> Sprachpaket - weil viele Englisch halt nicht perfekt beherschen>> Ich meine mich zu erinnern, daß GCC bei entsprechender Spracheinstellung> tatsächlich deutsche Meldungen ausgibt
Ja. Wie die meisten GNU-Programme ist gcc internationalisiert.
Allerdings scheint das bei den meisten Entwicklern so verhasst zu sein,
dass es oft bei den fertigen Toolchains schon gleich absichtlich
totgelegt wurde.
> - was einer der Gründe ist, wieso ich mein System auf englisch eingestellt> habe.
"I rest my case" ;-)