Forum: Compiler & IDEs Unbekannte Warnung


von Ingo Less (Gast)


Lesenswert?

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.
1
// Scale Values
2
  uint32_t CO2_Temp = (uint32_t)((((uint32_t)SensorRawData[0]) << 24) |
3
            (((uint32_t)SensorRawData[1]) << 16) |
4
            (((uint32_t)SensorRawData[3]) << 8) |
5
            ((uint32_t)SensorRawData[4]));
6
  SCD30_CO2_Concentration = *(float*)&CO2_Temp;
mit
1
static volatile float SCD30_CO2_Concentration;

Warum? Das ist der Code aus dem Manual zum umcasten.

von Oliver S. (oliverso)


Lesenswert?

Tja, da ist der Code aus dem Manual halt nicht ganz astrein.
Unbekannt ist die Warnung nicht, google kennt die.

Oliver
P.S. Falls google kaputt ist,
https://stackoverflow.com/questions/8824622/fix-for-dereferencing-type-punned-pointer-will-break-strict-aliasing

: Bearbeitet durch User
von Blume (Gast)


Lesenswert?

Ertmal toll das es leute gibt die Warnungen ernst nehmen.

so eine Warung kannst du durch den Einsatz einer
1
 union
 oder durch
1
 memcpy
 vermeiden.

von Nop (Gast)


Lesenswert?

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.

von Ingo Less (Gast)


Lesenswert?

Ok,
danke erstmal. Mit einer union geht es schief:
1
union{
2
 uint32_t CO2_Temp;
3
 float CO2;
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

von mh (Gast)


Lesenswert?

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

von Ingo Less (Gast)


Lesenswert?

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

von Rolf M. (rmagnus)


Lesenswert?

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.

von Stefan (Gast)


Lesenswert?

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_t CO2_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
  float SCD30_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

von Stefan (Gast)


Lesenswert?

natürlich ist ein Typo drin...

ShiCe..
1
uint32_t* CO2_TempPtr = &CO2_Temp;

hätte es heißen müssen...

von Oliver S. (oliverso)


Lesenswert?

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

von Stefan (Gast)


Lesenswert?

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_t value = 12345; // macht als float keinen Sinn...
2
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

von mh (Gast)


Lesenswert?

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.

von Rolf M. (rmagnus)


Lesenswert?

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.

: Bearbeitet durch User
von Stefan (Gast)


Lesenswert?

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

von Stefan (Gast)


Lesenswert?

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.

von mh (Gast)


Lesenswert?

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.

von Stefan (Gast)


Lesenswert?

vermute mal hübsch weiter...

von Ingo Less (Gast)


Lesenswert?

Vielen Dank erstmal. Könnte mir jemand von euch Profis nun eine absolut 
korrekte Lösung nennen, die keinerlei Probleme erzeugen könnte?

von mh (Gast)


Lesenswert?

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.

von Ingo Less (Gast)


Lesenswert?

Ok, Montag berichte ich!

von Rolf M. (rmagnus)


Lesenswert?

Ja, wurde ja oben auch schon genannt. Also:
1
memcpy(&SCD30_CO2_Concentration, &CO2_Temp, sizeof SCD30_CO2_Concentration);

von Stefan (Gast)


Lesenswert?

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

von Rolf M. (rmagnus)


Lesenswert?

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.

von mh (Gast)


Lesenswert?

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

von Rolf M. (rmagnus)


Lesenswert?

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

von mh (Gast)


Lesenswert?

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

😏

von Stefan (Gast)


Lesenswert?

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.

von mh (Gast)


Lesenswert?

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.

von Rolf M. (rmagnus)


Lesenswert?

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.

von Alex (Gast)


Lesenswert?

Moin,

Habe nur den Anfang gelesen... aber:

            ( (uint32_t)SensorRawData[0] << 24 ) +
            ( (uint32_t)SensorRawData[1] << 16 ) +
            ( (uint32_t)SensorRawData[3] <<  8 ) +
            ( (uint32_t)SensorRawData[4]       );

0,1, 3,4...? kommt mir komisch vor.

schönen Gruß,
Alex

von mh (Gast)


Lesenswert?

Alex schrieb:
> 0,1, 3,4...? kommt mir komisch vor.

Es ist komisch, aber nicht falsch. 2 ist nen CRC Wert

von Stefan (Gast)


Lesenswert?

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

von Nop (Gast)


Lesenswert?

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.

von Stefan (Gast)


Lesenswert?

Nop schrieb:
> Dunning-Kruger.

du meinst wohl dich... Ha Ha Ha...

von Kaj (Gast)


Lesenswert?

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?

von Nop (Gast)


Lesenswert?

Stefan schrieb:

> du meinst wohl dich... Ha Ha Ha...

Einer von uns weiß, was im C-Standard steht, und was das bedeutet - und 
Du bist es nicht.

von mh (Gast)


Lesenswert?

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.

von Stefan (Gast)


Lesenswert?

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

von mh (Gast)


Lesenswert?

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

von Stefan (Gast)


Lesenswert?

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

von Johannes S. (Gast)


Lesenswert?

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.

von Stefan (Gast)


Lesenswert?

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

von Jemand (Gast)


Lesenswert?

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.

von mh (Gast)


Lesenswert?

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.

von Stefan (Gast)


Lesenswert?

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

von Stefan (Gast)


Lesenswert?

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

von Jemand (Gast)


Lesenswert?

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?
1
// Scale Values
2
union {
3
  uint32_t u;
4
  float f;
5
} Temp;
6
Temp.u = (uint32_t)((((uint32_t)SensorRawData[0]) << 24) |
7
         (((uint32_t)SensorRawData[1]) << 16) |
8
         (((uint32_t)SensorRawData[3]) << 8) |
9
         ((uint32_t)SensorRawData[4]));
10
11
SCD30_CO2_Concentration = Temp.f;

von Jemand (Gast)


Lesenswert?

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.

von Jemand (Gast)


Lesenswert?

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.

von Jemand (Gast)


Lesenswert?

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.

von Jemand (Gast)


Lesenswert?

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.

von Jemand (Gast)


Lesenswert?

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.

von Stefan (Gast)


Lesenswert?

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

von Stefan (Gast)


Lesenswert?

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

von Stefan (Gast)


Lesenswert?

Jemand schrieb:
> Die union wird hier
> https://gcc.gnu.org/onlinedocs/gcc-4.9.1/gcc/Optimize-Options.html ja
> sogar als Positivbeispiel genannt.

Ich würde sie jedenfalls dem cast vorziehen.

Sie ist lesbarer und erfüllt denselben Zweck.

von mh (Gast)


Lesenswert?

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
inline float convert(uint32_t val) {
2
    float tmp;
3
    memcpy(&tmp, &val, 4);
4
    return tmp;
5
}
6
SCD30_CO2_Concentration = convert(CO2_Temp);
Da macht der Compiler dann eine 4 Byte move draus. Genauso für
1
float convert(uint32_t val) {
2
    union {
3
    uint32_t u;
4
    float f;
5
    } Temp;
6
7
    Temp.u = val;
8
    return Temp.f;
9
}

Alternativ kann man auch gleich die ganze Sensor-char-Array nach 
CO2-float in einer Funktion zusammenfassen mit
1
float convert(uint8_t *src) {
2
    float tmp;
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
    return tmp;
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".

von Jemand (Gast)


Lesenswert?

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_t f(uint32_t *a, float32_t *b) {
2
    // hierfür wird b aus dem Speicher in ein Register geladen
3
    float32_t c = *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
    return c;
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 :) .

von Stefan (Gast)


Lesenswert?

ja, so kann man das auch erklären.
find ich besser als meine Erklärung...

von mh (Gast)


Lesenswert?

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

von Jemand (Gast)


Lesenswert?

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

von Jemand (Gast)


Lesenswert?

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.

von Johannes S. (Gast)


Lesenswert?

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.

von Stefan (Gast)


Lesenswert?

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.

von Jemand (Gast)


Lesenswert?

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
float a(uint32_t val) {
5
    float tmp;
6
    memcpy(&tmp, &val, 4);
7
    return tmp;
8
}
9
10
float b(uint32_t val) {
11
    union {
12
    uint32_t u;
13
    float f;
14
    } Temp;
15
    Temp.u = val;
16
    return Temp.f;
17
}
18
19
float c(uint8_t *src) {
20
    float tmp;
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
    return tmp;
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

von mh (Gast)


Lesenswert?

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.

von Johannes S. (Gast)


Lesenswert?

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.

von mh (Gast)


Lesenswert?

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.

von Wahrsager (Gast)


Lesenswert?

einfach die warnungen abschalten. die bringen einen eh nicht weiter und 
kosten zu viel zeit da nachzulesen

von Johannes S. (Gast)


Lesenswert?

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.

von mh (Gast)


Lesenswert?

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

von mh (Gast)


Lesenswert?

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
9
— a character type.

von Johannes S. (Gast)


Lesenswert?

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.

von mh (Gast)


Lesenswert?

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

von Nop (Gast)


Lesenswert?

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.

von MaWin (Gast)


Lesenswert?

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.

von Rolf M. (rmagnus)


Lesenswert?

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

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

von Jemand (Gast)


Lesenswert?

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.

von Praktiker (Gast)


Lesenswert?

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

von mh (Gast)


Lesenswert?

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.

von mh (Gast)


Lesenswert?

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.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Vielleicht noch einmal zur Klarstellung:

Ingo Less schrieb:
1
  SCD30_CO2_Concentration = *(float*)&CO2_Temp;

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
float foo(uint32_t *pu, float *pf) {
5
  *pf = 1.0;
6
  *pu = 0x40000000;
7
  return *pf;
8
}
9
 
10
int main(void) {
11
  uint32_t u;
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.

von Nop (Gast)


Lesenswert?

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.

von Jemand (Gast)


Lesenswert?

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.

von Jemand (Gast)


Lesenswert?

Der Rust-Compiler gibt sich aber zB Mühe, Fehlermeldungen zu erklären, 
aber die Sprache macht es ihm da auch teilweise etwas einfacher ;)

von Rolf M. (rmagnus)


Lesenswert?

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" ;-)

von Ingo Less (Gast)


Lesenswert?

Mit memcpy macht es was es soll, ohne Probleme. Danke!

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.