Mikrocontroller: "Compiler-Optimizer-Fehler" finden?

Johannes Bauer wrote:
On 10.03.2015 11:31, Stefan Reuther wrote:
Unsinn. Ich muss nur wissen: wenn ich einen Wert habe, der in den neuen
Typ reinpasst, bleibt der erhalten. Wenn er nicht passt, wird bei
ganzzahligen Typen mod-2**n gerechnet, unabhängig von der Repräsen-
tation. (Nichts anderes drĂźckt der Standard in vielen Worten aus.)

Falsch!

Der Standard definiert solche Konversionen nur fĂźr unsignierte
Datentypen.

Auch bei der Konvertierung unsigned -> signed gilt das nicht, aber ich
wollte es nicht zu sehr ausarten lassen. Wichtig ist: wenn es passt,
wird der Wert kopiert (und nicht, wie behauptet, die Repräsentation).

Es gibt eine Ausnahme, die i.W. besagt, dass ein 'char*' auf alles
zeigen darf, und memcpy kopiert chars.

memcpy kopiert natĂźrlich keine chars:

void *memcpy(void *dest, const void *src, size_t n);

"The memcpy function copies n characters ..." (ISO 9899:1999 7.21.2.1p2).

Sondern hat im Prototyp void*, der nicht zu den von dir o.g.
aliasing-Problemen fĂźhrt.

Einen void* kann man ja auch nicht dereferenzieren, er muss vor
Verwendung in einen anderen Typ gewandelt werden. Dieser andere Typ ist
halt ein Zeichentyp (char, unsigned char, signed char).

gcc macht hier den Schritt nach vorne und definiert das Verhalten fĂźr
unions. Das darf er tun. "Der Compiler definiert" ist eine mĂśgliche
(aber halt nicht verpflichtende) Instanz von "der Sprachstandard lässt
undefiniert".

Das ist der Unterschied zwischen "implementation defined" und "undefined".

"implementation defined" heißt, der Compiler muss es definieren. Bei
"undefined" muss er nicht (darf aber).


Stefan
 
On 2015-03-09, Heinz Saathoff <newshsaat@arcor.de> wrote:
Michael Schwingen schrieb:

On 2015-03-07, Stefan Reuther <stefan.news@arcor.de> wrote:
var = expr > 0;
:
if (var == TRUE) ...

Das heißt ja auch 'if (var)'.

aber nur, wenn expr unsigned ist.

Warum das? In C ist doch [...]

Du hast Recht, ich bin in der Zeile verrutscht und dachte, Du hättest
if (expr)
gemeint.

cu
Michael
 
Hallo Stefan,

Du schriebst am Tue, 10 Mar 2015 11:31:21 +0100:

int i = *(int*) &floatVar;
....
Der Compiler darf so tun, als ob die Zeiger typspezifisch wären, auch,
wenn sie es praktisch - als Bitmuster im Register - nicht sind. Er darf
also davon ausgehen, dass ein Zeiger vom Typ 'int*' niemals auf eine
Speicherstelle zeigt, die als 'float' initialisiert wurde. Und das wird
in obigem Schnipsel verletzt.

Er _darf_ davon ausgehen, aber er _muß_ die Konversion akzeptieren und den
Ausgangszeiger als das interpretieren, was der Programmierer verlangt.
(Gut, nach Deinem Zitat zur Konvertierung kĂśnnte der Compilerbauer das so
auffassen, daß der ursprünglich auf eine Speicherstelle eines bestimmten
Typs zeigende Zeiger so umgebaut wird, daß ein Zeiger auf eine
Speicherstelle des Zieltyps herauskommt.)

Sicher geht das nur mit memcpy.

Nichtmal unbedingt damit, wenn's so extrem wird wie oben angedacht.

Es gibt eine Ausnahme, die i.W. besagt, dass ein 'char*' auf alles
zeigen darf, und memcpy kopiert chars.

Eigentlich ist der "Universalzeigertyp", der mit _jedem_ Zeigertyp
kompatibel sein _muß_, doch der "void *"?
D.h. Dein Beispiel "int i = *(int*) &floatVar;" ließe sich damit in die
zulässige Form "int i = *((int*) ((void *) &floatVar));" bringen.

Muß nicht bei einer "union" sichergestellt sein, daß die Varianten alle
denselben Speicherplatz belegen?

Muss es, aber der Lesezugriff auf eine andere Variante als die, die
initialisiert wurde, ist halt undefiniert.

Das Ergebnis der Interpretation des Inhalts einer Variablen mit einem
anderen Typ als deklariert ist doch immer undefiniert, weil prinzipiell
implementationsabhängig? Nicht implementationabhängig kann da allenfalls
sein, wie die Speicherbelegung vorzunehmen ist.
(Hier ist C strikter definiert als Pascal - in Pascal _darf_ der Compiler
bei einem varianten Record [entspricht der C-union] jeder Variante einen
separaten Speicherberich zuordnen, darf sie aber auch Ăźbereinanderlegen.)

gcc macht hier den Schritt nach vorne und definiert das Verhalten fĂźr
unions. Das darf er tun. "Der Compiler definiert" ist eine mĂśgliche

Der spezifische Compiler muß wohl oder übel die Implementation der
Datentypen definieren, sogar wenn die nicht dokumentiert werden. Da macht
der gcc garnichts besonderes, nur das notwendige.

--
--
(Weitergabe von Adressdaten, Telefonnummern u.ä. ohne Zustimmung
nicht gestattet, ebenso Zusendung von Werbung oder ähnlichem)
-----------------------------------------------------------
Mit freundlichen Grüßen, S. Schicktanz
-----------------------------------------------------------
 
Hallo Johannes,

Du schriebst am Tue, 10 Mar 2015 11:12:51 +0100:

Und was "schreibt man" fĂźr "res= var1 == var2;", wenn var1 und var2
keine sicheren booleschen Werte sind, das Ergebnis aber einer sein soll?

Wie kommst du denn darauf das das Ergebnis von "var1 == var2" nicht ein
sicherer boolescher Wert ist? Laut C Standard kommt da immer ein Wert
raus, der als bool funktioniert.

Klar, aber die Frage war ja auch nicht, welchen Typ das _Ergebnis_ hat,
sondern welchen Wert der Ausdruck liefert, je nachdem ob die Operanden
boolesche Werte "sein sollen".

Da bleibt dann nur so eine Hilfskonstruktion wie "res= !var1 == !var2;".
SchĂśn und einsichtig ist anders. Und das ist nur einer der mĂśglichen
Fälle.

Habe ich in der Praxis noch nicht gesehen und ist auch unnĂśtig. Im

Nee, Ăźberhaupt nicht unnĂśtig, sondern sogar sehr wichtig und immer wieder
in Hinmweisen zu finden.

Ăźbrigen kann man natĂźrlich auch casten: (bool)(var1 == var2). Habe ich
aber auch noch nie gesehen.

Das ist ja auch unnÜtig. Allenfalls wäre sowas mÜglich:

res= (bool) var1 == (bool) var2;

Zeig doch mal ein vollständiges Beispiel, wo der von dir geschilderte
fehlde "boolesche Verlgiechsoperatur" tatsächlich ein Problem macht.
^en

int var1; float var2;
....
var1= 1234;
....
var2= 123.4; var2 *= 10;
....
res= var1 == var2;

Soll "res" hier wiedergeben, ob die _numerischen_ Werte gleich sind (was
sie ziemlich sicher nicht sein werden), oder ob die numerischen Werte beide
0 oder nicht 0 sind?

--
--
(Weitergabe von Adressdaten, Telefonnummern u.ä. ohne Zustimmung
nicht gestattet, ebenso Zusendung von Werbung oder ähnlichem)
-----------------------------------------------------------
Mit freundlichen Grüßen, S. Schicktanz
-----------------------------------------------------------
 
On 12.03.2015 00:15, Sieghard Schicktanz wrote:

Eigentlich ist der "Universalzeigertyp", der mit _jedem_ Zeigertyp
kompatibel sein _muß_, doch der "void *"?

Nein, void* muss nur mit jedem Zeiger kompatibel sein, der auf *Daten*
zeigt.

void* muss insbesondere nicht zuweisungskompatibel mit Funktionszeigern
sein. Da gilt eine andere Regel: Jeder Funktionszeiger muss von der
größe her jeden anderen Funktionszeiger halten könnnen.

D.h. Dein Beispiel "int i = *(int*) &floatVar;" ließe sich damit in die
zulässige Form "int i = *((int*) ((void *) &floatVar));" bringen.

Leider nicht :)

Gruß,
Johannes

--
Wo hattest Du das Beben nochmal GENAU vorhergesagt?
Zumindest nicht Ăśffentlich!
Ah, der neueste und bis heute genialste Streich unsere großen
Kosmologen: Die Geheim-Vorhersage.
- Karl Kaos Ăźber RĂźdiger Thomas in dsa <hidbv3$om2$1@speranza.aioe.org>
 
Sieghard Schicktanz schrieb:

Zeig doch mal ein vollständiges Beispiel, wo der von dir geschilderte
fehlde "boolesche Verlgiechsoperatur" tatsächlich ein Problem macht.
^en

int var1; float var2;
...
var1= 1234;
...
var2= 123.4; var2 *= 10;
...
res= var1 == var2;

Soll "res" hier wiedergeben, ob die _numerischen_ Werte gleich sind (was
sie ziemlich sicher nicht sein werden), oder ob die numerischen Werte beide
0 oder nicht 0 sind?

Hier werden aber numerische Werte auf Gleichheit getestet, was ich so
auch erwarten würde. Wenn ich prüfen will, ob beide vars ungleich 0
sind, würde ich das als
res = var1 != 0 && var2 != 0;
oder verkürzt als
res = var1 && var2;
schreiben.


- Heinz
 
Sieghard Schicktanz wrote:
int i = *(int*) &floatVar;
...
Der Compiler darf so tun, als ob die Zeiger typspezifisch wären, auch,
wenn sie es praktisch - als Bitmuster im Register - nicht sind. Er darf
also davon ausgehen, dass ein Zeiger vom Typ 'int*' niemals auf eine
Speicherstelle zeigt, die als 'float' initialisiert wurde. Und das wird
in obigem Schnipsel verletzt.

Er _darf_ davon ausgehen, aber er _muß_ die Konversion akzeptieren und den
Ausgangszeiger als das interpretieren, was der Programmierer verlangt.

Richtig. Das einzige, was du mit dem konvertierten Zeiger dann tun
darfst, ist aber, ihn wieder zurĂźckzukonvertieren (...oder halt in einen
char* zu wandeln und/oder in memcpy reinzustecken). Dereferenzieren
darfst du ihn nicht.

Sicher geht das nur mit memcpy.

Nichtmal unbedingt damit, wenn's so extrem wird wie oben angedacht.

Es gibt eine Ausnahme, die i.W. besagt, dass ein 'char*' auf alles
zeigen darf, und memcpy kopiert chars.

Eigentlich ist der "Universalzeigertyp", der mit _jedem_ Zeigertyp
kompatibel sein _muß_, doch der "void *"?

'void*' und 'char*' sind gleich mächtig.

Muß nicht bei einer "union" sichergestellt sein, daß die Varianten alle
denselben Speicherplatz belegen?

Muss es, aber der Lesezugriff auf eine andere Variante als die, die
initialisiert wurde, ist halt undefiniert.

Das Ergebnis der Interpretation des Inhalts einer Variablen mit einem
anderen Typ als deklariert ist doch immer undefiniert, weil prinzipiell
implementationsabhängig?

Es gibt einen Unterschied zwischen undefiniert und implementations-
abhängig. Es ist implementationsabhängig, welches Bitmuster sich hinter
einem float verbirgt. Folglich ist implementationsabhängig, welche
Ausgabe der Compiler fĂźr
float f = 1.0;
unsigned int i;
assert(sizeof(i) == sizeof(f));
memcpy(&i, &f, sizeof(f));
printf("%x\n", i);
bringt. Meistens wird es '3f800000' sein.

Dahingegen ist undefiniert, was bei
float f = 1.0;
unsigned int i;
assert(sizeof(i) == sizeof(f));
i = *(unsigned int*)&f;
printf("%x\n", i);
rauskommt. Die Funktion enthält keinen Lesezugriff auf eine float-
Variable, also darf der Compiler alle Schreibzugriffe auf float-
Variablen wegoptimieren.

Klingt komisch, ist aber so. In diesem Trivialfall wird er das nicht
tun, relevant wird das aber, wenn größere Funktionen mit Pointer-
Parametern im Spiel sind, wenn die Frage zu klären ist: kann es
vorkommen, dass parameter1 auf den gleichen Wert zeigt wie parameter2?


Stefan
 
Hallo Johannes,

Du schriebst am Thu, 12 Mar 2015 09:11:10 +0100:

Nein, void* muss nur mit jedem Zeiger kompatibel sein, der auf *Daten*
zeigt.

Ok, das ist sinnvoll.

zulässige Form "int i = *((int*) ((void *) &floatVar));" bringen.

Leider nicht :)

Wieso nicht? Ich sehe da keinen Widerspruch - "&floatVar" ist doch
schließlich ein _gültiger_ Zeiger, der auf *Daten* zeigt.

--
--
(Weitergabe von Adressdaten, Telefonnummern u.ä. ohne Zustimmung
nicht gestattet, ebenso Zusendung von Werbung oder ähnlichem)
-----------------------------------------------------------
Mit freundlichen Grüßen, S. Schicktanz
-----------------------------------------------------------
 
Hallo Stefan,

Du schriebst am Thu, 12 Mar 2015 12:44:41 +0100:

Er _darf_ davon ausgehen, aber er _muß_ die Konversion akzeptieren und
....
Richtig. Das einzige, was du mit dem konvertierten Zeiger dann tun
darfst, ist aber, ihn wieder zurĂźckzukonvertieren (...oder halt in einen
char* zu wandeln und/oder in memcpy reinzustecken). Dereferenzieren
darfst du ihn nicht.

D.h. der Compiler dĂźrfte sich aktiv dagegen wehren, eine solchen Zeiger zu
seinem Zweck benutzen zu wollen (zum Zeigen)?
(Tut das einer?)
Der Compiler müßte dazu allerdings den Source-Text recht genau "verstehen"
- schließlich _muß_ er ja einen zu irgendwas anderem gecasteten "void *"
aus z.B. einem "malloc"-Aufruf zum Zugriff auf den Speicher akzeptieren.

....
> 'void*' und 'char*' sind gleich mächtig.

Aber ein "char *" _kĂśnnte_ inkompatibel zu einem anderen als "void *" sein.
Ein "void *" _darf_ nicht inkompatibel sein.

Es gibt einen Unterschied zwischen undefiniert und implementations-
abhängig. Es ist implementationsabhängig, welches Bitmuster sich hinter
....
Dahingegen ist undefiniert, was bei
float f = 1.0;
unsigned int i;
assert(sizeof(i) == sizeof(f));
i = *(unsigned int*)&f;
printf("%x\n", i);
rauskommt. Die Funktion enthält keinen Lesezugriff auf eine float-
Variable, also darf der Compiler alle Schreibzugriffe auf float-
Variablen wegoptimieren.

SchĂśn, aber was hat das mit der Speicherbelegung einer "union" zu tun?
Daß der Code, der nach einer Optimierung herauskommt, nicht mehr
"buchstabengetreu" dem Source-Code entsprechen muß, ist ja bekannt.
Ist nach Deinem Verständnis und deiner Kenntnis mit

union test {
int whole;
float real;
};
int Ganzzahl; float Bruchzahl; union test Testwert;

der Code

Testwert.whole= 1234; Bruchzahl= Testwert.real;
oder
Testwert.real= 12.34; Ganzzahl= Testwert.whole;

_imkplementationsabhängig_ oder _undefiniert_?

Klingt komisch, ist aber so. In diesem Trivialfall wird er das nicht
tun, relevant wird das aber, wenn größere Funktionen mit Pointer-
Parametern im Spiel sind, wenn die Frage zu klären ist: kann es
vorkommen, dass parameter1 auf den gleichen Wert zeigt wie parameter2?

Ja, das Zeigerspiel kann in manchen Fällen recht verwirrend sein...

--
--
(Weitergabe von Adressdaten, Telefonnummern u.ä. ohne Zustimmung
nicht gestattet, ebenso Zusendung von Werbung oder ähnlichem)
-----------------------------------------------------------
Mit freundlichen Grüßen, S. Schicktanz
-----------------------------------------------------------
 
Hallo Heinz,

Du schriebst am Thu, 12 Mar 2015 12:35:35 +0100:

int var1; float var2;
....
res= var1 == var2;
....
Hier werden aber numerische Werte auf Gleichheit getestet, was ich so

Hier geht C davon aus, daß ... - aber vielleicht hat das der Programmierer
Ăźbersehen, und er mĂśchte nur wissen, ob nicht nur eine der beiden Variablen
0 ist?

auch erwarten wĂźrde. Wenn ich prĂźfen will, ob beide vars ungleich 0
sind, wĂźrde ich das als

Ja, schon recht, aber das _will_ der Test ja garnicht prĂźfen, der "will"
prĂźfen, ob entweder _beide_ Variablen 0 sind oder _keine_. Und das ist ein
Test auf "boolesche Gleichheit", den es so in C nicht gibt.

--
--
(Weitergabe von Adressdaten, Telefonnummern u.ä. ohne Zustimmung
nicht gestattet, ebenso Zusendung von Werbung oder ähnlichem)
-----------------------------------------------------------
Mit freundlichen Grüßen, S. Schicktanz
-----------------------------------------------------------
 
On 13.03.2015 05:03, Sieghard Schicktanz wrote:
Hallo Heinz,

Du schriebst am Thu, 12 Mar 2015 12:35:35 +0100:

int var1; float var2;
....
res= var1 == var2;
....
Hier werden aber numerische Werte auf Gleichheit getestet, was ich so

Hier geht C davon aus, daß ... - aber vielleicht hat das der Programmierer
Ăźbersehen, und er mĂśchte nur wissen, ob nicht nur eine der beiden Variablen
0 ist?

auch erwarten wĂźrde. Wenn ich prĂźfen will, ob beide vars ungleich 0
sind, wĂźrde ich das als

Ja, schon recht, aber das _will_ der Test ja garnicht prĂźfen, der "will"
prĂźfen, ob entweder _beide_ Variablen 0 sind oder _keine_. Und das ist ein
Test auf "boolesche Gleichheit", den es so in C nicht gibt.
Irgendwie verstehe ich Dein Problem jetzt nicht. Wenn Du testen willst,
ob beide ==0 oder !=0 sind, dann schreibe es doch so hin:
res = (var1 == 0) == (var2 == 0);
Das liefert genau das gewßnschte Ergebnis und ist klar verständlich.

Alles andere verstehst Du selbst ohne zusätzlichen Kommentar nach einem
Jahr nicht mehr und andere erst gar nicht.

Einen einen zusätzlichen Operator einzufßhren, nur um diesen Test
hinzukriegen, macht fĂźr keinen Sinn. Sowas kommt alle Jubeljahre mal vor
und dann kennt es keiner.

Die Diskussion startete mit der falschen Behauptung, C kenne keinen
Vergleich zwischen boolschen Werten. Das obige ist fĂźr mich ziemlich
zusammenkonstruiert.

--
Reinhardt
 
On 2015-03-12, Sieghard Schicktanz <Sieghard.Schicktanz@SchS.de> wrote:
Richtig. Das einzige, was du mit dem konvertierten Zeiger dann tun
darfst, ist aber, ihn wieder zurückzukonvertieren (...oder halt in einen
char* zu wandeln und/oder in memcpy reinzustecken). Dereferenzieren
darfst du ihn nicht.

D.h. der Compiler dürfte sich aktiv dagegen wehren, eine solchen Zeiger zu
seinem Zweck benutzen zu wollen (zum Zeigen)?
(Tut das einer?)

Ja - in dem Sinn, daß der Optimizer in so einem Fall nicht das erzeugen muß,
was Du eigentlich wolltest - im Extremfall bleibt von Deinem Code nichts
übrig.

Der Compiler müßte dazu allerdings den Source-Text recht genau "verstehen"
- schließlich _muß_ er ja einen zu irgendwas anderem gecasteten "void *"
aus z.B. einem "malloc"-Aufruf zum Zugriff auf den Speicher akzeptieren.

Ich habe da heute mal etwas 'rumgespielt. Ich hatte vor ein paar Jahren
Probleme mit Code der Art:

uint16_t hash(uint32_t data)
{
uint16_t foo = *(uint16_t *) &data;

return foo[0] ^ foo[1];
}

Das sollte ja nach Deiner Interpretation OK sein, richtig?

Was der Compiler klassisch tun müßte, ist:
- data auf den Stack legen, um "&data" bestimmen zu können
- die Adresse nehmen und 2 16-Bit Loads erzeugen und die Ergebnisse XOR-verknüpfen

(Beispiel aus existierendem, altem Code, der tatsächlich irgendwann auf die
Nase gefallen ist).

Ab einer gewissen Version ist gcc auf die Idee gekommen, daß die 2
Operationen "data (als Register übergeben) auf lokale Variable auf Stack
schieben" und "16Bit-Werte aus foo[0] und foo[1] lesen" ja unabhängig
voneinander sind - und demnach umsortiert werden dürfen: erst die 2 Loads,
dann der store, weil das besser für die Pipeline ist. Damit ist das Ergebnis
der Funktion natürlich zufällig und hat nichts mehr mit dem übergebenen
Parameter zu tun.

Nach meinem Bugreport hat man mir dann erklärt, daß der Compiler Recht hat:
foo und &data *dürfen* laut Sprachstandard nicht auf die gleiche Adresse
zeigen, also darf der Compiler das umsortieren.

Aber zum Verständnis des Codes durch den Compiler: ein relativ aktueller gcc
"versteht" den Code soweit, daß er überhaupt nichts mehr auf den Stack
schiebt, und stattdessen
return (data ^ (data>>16);
erzeugt (was die korrekte Lösung ist und außerdem effizienter als das
Pointergefrickel, also sollte man das auch gleich so im Source hinschreiben).

Dabei macht es keinen Unterschied, ob man die Zeiger per typecast oder union
umwandelt.

(alle Tests auf big-endian-PowerPC).

cu
Michael
 
Sieghard Schicktanz wrote:
Du schriebst am Thu, 12 Mar 2015 12:44:41 +0100:
Er _darf_ davon ausgehen, aber er _muß_ die Konversion akzeptieren und
...
Richtig. Das einzige, was du mit dem konvertierten Zeiger dann tun
darfst, ist aber, ihn wieder zurĂźckzukonvertieren (...oder halt in einen
char* zu wandeln und/oder in memcpy reinzustecken). Dereferenzieren
darfst du ihn nicht.

D.h. der Compiler dĂźrfte sich aktiv dagegen wehren, eine solchen Zeiger zu
seinem Zweck benutzen zu wollen (zum Zeigen)?

DĂźrfte er.

'void*' und 'char*' sind gleich mächtig.

Aber ein "char *" _kĂśnnte_ inkompatibel zu einem anderen als "void *" sein.

Nein. (ISO 9899:1999 §6.2.3.2p7, erlaubt im Wesentlichen das gleiche fßr
char* wie p1 fĂźr void*. Der C++-Standard geht sogar soweit, fĂźr beide
die gleiche Repräsentation zu fordern.)

Dahingegen ist undefiniert, was bei
float f = 1.0;
unsigned int i;
assert(sizeof(i) == sizeof(f));
i = *(unsigned int*)&f;
printf("%x\n", i);
rauskommt. Die Funktion enthält keinen Lesezugriff auf eine float-
Variable, also darf der Compiler alle Schreibzugriffe auf float-
Variablen wegoptimieren.

SchĂśn, aber was hat das mit der Speicherbelegung einer "union" zu tun?
Daß der Code, der nach einer Optimierung herauskommt, nicht mehr
"buchstabengetreu" dem Source-Code entsprechen muß, ist ja bekannt.
Ist nach Deinem Verständnis und deiner Kenntnis mit

union test {
int whole;
float real;
};
int Ganzzahl; float Bruchzahl; union test Testwert;

der Code
Testwert.whole= 1234; Bruchzahl= Testwert.real;
oder
Testwert.real= 12.34; Ganzzahl= Testwert.whole;

_imkplementationsabhängig_ oder _undefiniert_?

Undefiniert (Verletzung des shall-constraints aus §6.5p7), jedenfalls in
der 1999er Version. Wenn ich Johannes richtig verstanden habe, gilt das
in der 2011er Version nicht mehr und dort ist der Zugriff Ăźber die Union
erlaubt. (Aber der Ăźber den Pointer wohl weiterhin nicht.)


Stefan
 
Hallo Michael,

Du schriebst am 12 Mar 2015 23:07:04 GMT:

D.h. der Compiler dĂźrfte sich aktiv dagegen wehren, eine solchen Zeiger
zu seinem Zweck benutzen zu wollen (zum Zeigen)?
....
Ja - in dem Sinn, daß der Optimizer in so einem Fall nicht das erzeugen
muß, was Du eigentlich wolltest - im Extremfall bleibt von Deinem Code
nichts Ăźbrig.

Ok - die Optimiererei ist da sowieso ein eigenes Kapitel. Da mĂźssen soviele
Annahmen getroffen werden, _was_ der aufgeschriebene Code-Text eigentlich
tun soll, daß es für den Optimierer(programmierer) recht schwierig ist, das
zu treffen, was der Code-Schreiber wollte. Das ist halt das "Ăźbliche"
Dilemma zwischen dem, was man _meint_ und dem was man "sagt", um es zu
beschreiben, "unmißverständlich", möglichst...

Der Compiler müßte dazu allerdings den Source-Text recht genau
"verstehen"

und das tut er halt in den wenigsten Fällen. Und sogar dann kann er die
Intention des Schreibers falsch verstehen. Kommt ja sogar unter Menschen
vor...

uint16_t hash(uint32_t data)
{
uint16_t foo = *(uint16_t *) &data;

return foo[0] ^ foo[1];
}
Was der Compiler klassisch tun müßte, ist:
- data auf den Stack legen, um "&data" bestimmen zu kĂśnnen

Muß nicht per Stack passieren.

- die Adresse nehmen und 2 16-Bit Loads erzeugen und die Ergebnisse
XOR-verknĂźpfen

Nachdem in C von _jedem_ Datenobjekt die Adrese bestimmbar sein muß, ist
das so zu erwarten.
....
Nach meinem Bugreport hat man mir dann erklärt, daß der Compiler Recht
hat: foo und &data *dĂźrfen* laut Sprachstandard nicht auf die gleiche
Adresse zeigen, also darf der Compiler das umsortieren.

Hat man Dir auch die Stelle im Sprachstandard genannt, an der das steht?
Das widerspricht schließlich direkt der Forderung nach der Adressierbarkeit
von Datenobjekten, der Konsistenz von Zuweisungen und der Einhaltung der
Reihenfolge der Anweisungen. Und hier sind die beiden Anweisungen durch die
Sequenzierung mit dem ";" sogar explizit in ihrer Reihenfolge festgelegt.
Aber hier gilt wohl schon das Gewohnheitsrecht der Optimiererprogrammierer
- der Optimierer hat immer Recht, auch wenn der erzeugte Code dem Source-
Text widerspricht.
Andererseits ist C ja auch die Programmiersprache, die auf Compiler- und
Prozessor-Freundlichkeit optimiert ist.

Aber zum Verständnis des Codes durch den Compiler: ein relativ aktueller
gcc "versteht" den Code soweit, daß er überhaupt nichts mehr auf den
Stack schiebt, und stattdessen
return (data ^ (data>>16);
erzeugt (was die korrekte Lösung ist und außerdem effizienter als das
Pointergefrickel, also sollte man das auch gleich so im Source
hinschreiben).

Ja, das wäre (beides) besser - aber was wolltest Du jatzt dazu schreiben?

Dabei macht es keinen Unterschied, ob man die Zeiger per typecast oder
union umwandelt.

Eine union wandelt nur keine Zeiger um, sondern benutzt einen
Speicherbereich auf unterschiedliche Art. Wenn das benutzt wird, um
einen (Satz) Wert(e) in einer bestimmten Darstellung einzutragen (z.B. als
Felder eines Registers) und in einer anderen weiterzubearbeiten (z.B. um
das Register mit einem einzigen Zugriff beschreiben zu kĂśnnen), dann ist es
eben _sehr_ wichtig, was der Compiler und ggfs. ein Optimierer dann daraus
macht.
Aber C - obwohl mal als "besserer Assembler" verschrien - entwickelt sich
immer mehr weg von der Hardware und wird, trotz solcher "Features" wie
"volatile", "static" oder "inline", immer weniger fĂźr solche Software
brauchbar, die nahe an der Hardware arbeiten müß.

--
--
(Weitergabe von Adressdaten, Telefonnummern u.ä. ohne Zustimmung
nicht gestattet, ebenso Zusendung von Werbung oder ähnlichem)
-----------------------------------------------------------
Mit freundlichen Grüßen, S. Schicktanz
-----------------------------------------------------------
 
Hallo Reinhardt,

Du schriebst am Fri, 13 Mar 2015 06:47:49 +0800:

Irgendwie verstehe ich Dein Problem jetzt nicht. Wenn Du testen willst,
ob beide ==0 oder !=0 sind, dann schreibe es doch so hin:
res = (var1 == 0) == (var2 == 0);
Das liefert genau das gewßnschte Ergebnis und ist klar verständlich.

Sicher. Es zeigt halt die Konsistenz der Sprache - "if (var1 == 0)" wird
als unschĂśn angesehen, man soll mĂśglichst "if (!var1)" schreiben, aber an
anderen Stellen "schießt man sich damit in den Fuß". Man muß halt ständig
aufpassen, was der Compiler jeweils fĂźr Annahmen Ăźber den Typ der AusdrĂźcke
trifft, damit man am Ende nach der ganzen Konversions-Orgie ein Ergebnis
erhält, das dem erwarteten entspricht.

Die Diskussion startete mit der falschen Behauptung, C kenne keinen
Vergleich zwischen boolschen Werten. Das obige ist fĂźr mich ziemlich
zusammenkonstruiert.

Nein, das war _nicht_ die Behauptung. Die Behauptung war, C kann nicht
erkennen, ob ein Vergleich zweier Variabler ihren arithmetischen oder ihren
booleschen Wert (den sie jeweils _gleichzeitig_ besitzen) betreffen soll.

--
--
(Weitergabe von Adressdaten, Telefonnummern u.ä. ohne Zustimmung
nicht gestattet, ebenso Zusendung von Werbung oder ähnlichem)
-----------------------------------------------------------
Mit freundlichen Grüßen, S. Schicktanz
-----------------------------------------------------------
 
Am 12.03.2015 um 22:03 schrieb Sieghard Schicktanz:

Ja, schon recht, aber das _will_ der Test ja garnicht prĂźfen, der "will"
prĂźfen, ob entweder _beide_ Variablen 0 sind oder _keine_. Und das ist ein
Test auf "boolesche Gleichheit", den es so in C nicht gibt.

Doch, den gibt es. Aber nur mit boolschen Variablen (die definierte Werte
tragen). Deine Version benutzt aber int, also numerische Werte, die muß man
dann zunächst auf boolsche Werte konvertieren.
Aber wie gesagt, dieser Fall ist zumindest mir recht unbekannt in der
Entwicklungspraxis.

Bernd

--
Meine Glaskugel ist mir leider unvorhersehbarerweise vom Balkon gefallen.
P.Liedermann in defa
 
Johannes Bauer wrote:
Sieghard Schicktanz wrote:

Eigentlich ist der "Universalzeigertyp", der mit _jedem_ Zeigertyp
kompatibel sein _muß_, doch der "void *"?

Nein, void* muss nur mit jedem Zeiger kompatibel sein, der auf *Daten*
zeigt.

void* muss insbesondere nicht zuweisungskompatibel mit Funktionszeigern
sein.

Das ist ja auch sinnvoll, denn auf einer Maschine mit Harvard-
Architektur zeigt ein Funktionszeiger in einen anderen Adressraum als
ein Datenzeiger.

Da gilt eine andere Regel: Jeder Funktionszeiger muss von der
größe her jeden anderen Funktionszeiger halten könnnen.

Test mit "void(*)(void)":
------------------------------------------------------------------------
#include <stdlib.h>
#include <stdio.h>

void test(const char* s)
{
printf("%s\n", s);
}

int main(void)
{
const char* t = "Test";
void(*p)(void) = (void(*)(void)) test;

((void(*)(const char*)) p)(t);
exit(0);
}
------------------------------------------------------------------------
 
Sieghard Schicktanz schrieb:

Hat man Dir auch die Stelle im Sprachstandard genannt, an der das steht?
Das widerspricht schließlich direkt der Forderung nach der Adressierbarkeit
von Datenobjekten, der Konsistenz von Zuweisungen und der Einhaltung der
Reihenfolge der Anweisungen. Und hier sind die beiden Anweisungen durch die
Sequenzierung mit dem ";" sogar explizit in ihrer Reihenfolge festgelegt.

Nein! Da der Compiler davon ausgehen darf, dass 'foo' nicht auf 'data'
zeigt [1], kann er den Zugriff auf 'foo', das dann halt uninitialisiert
ist, auch vorziehen. Ein richtig schlauer Compiler optimiert die Routine
vermutlich gleich zu 'return 0;' um, denn wenn die zu lesenden Variable
eh uninitialisiert ist, kann die Routine ja ein beliebiges Ergebnis
zurĂźckgeben, also auch 0.

Christian

[1] Das nennt sich "strict aliasing":
"§6.5.7 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."
Michaels Beispiel findet sich auf
<http://dbp-consulting.com/tutorials/StrictAliasing.html> wieder.
--
Christian Zietz - CHZ-Soft - czietz (at) gmx.net
WWW: http://www.chzsoft.de/
PGP/GnuPG-Key-ID: 0x52CB97F66DA025CA / 0x6DA025CA
 
Sieghard Schicktanz wrote:
Du schriebst am Fri, 13 Mar 2015 06:47:49 +0800:
Irgendwie verstehe ich Dein Problem jetzt nicht. Wenn Du testen willst,
ob beide ==0 oder !=0 sind, dann schreibe es doch so hin:
res = (var1 == 0) == (var2 == 0);
Das liefert genau das gewßnschte Ergebnis und ist klar verständlich.

Sicher. Es zeigt halt die Konsistenz der Sprache - "if (var1 == 0)" wird
als unschĂśn angesehen, man soll mĂśglichst "if (!var1)" schreiben,

....sagt jetzt wer?

Wenn man mit Null vergleichen will, schreibt man einen Vergleich mit
Null auf. Ganz einfach.

MISRA Rule 13.2 "Tests of a value against zero should be made explicit,
unless the operand is effectively Boolean."


Stefan
 
Hallo Stefan,

Du schriebst am Sat, 14 Mar 2015 11:08:55 +0100:

Sicher. Es zeigt halt die Konsistenz der Sprache - "if (var1 == 0)" wird
als unschĂśn angesehen, man soll mĂśglichst "if (!var1)" schreiben,

...sagt jetzt wer?

Stand z.B. hier im Thread in <cm18jbFq954U1@mid.individual.net> effektiv da.
Christian schrieb zwar nicht 0, sondern "FALSE", was aber effektiv dasselbe
ist.

Wenn man mit Null vergleichen will, schreibt man einen Vergleich mit
Null auf. Ganz einfach.

MISRA Rule 13.2 "Tests of a value against zero should be made explicit,
unless the operand is effectively Boolean."

Ist ein eingeschränkter Standard als Referenz fßr die ßbliche Nutzung
brauchbar?

--
--
(Weitergabe von Adressdaten, Telefonnummern u.ä. ohne Zustimmung
nicht gestattet, ebenso Zusendung von Werbung oder ähnlichem)
-----------------------------------------------------------
Mit freundlichen Grüßen, S. Schicktanz
-----------------------------------------------------------
 

Welcome to EDABoard.com

Sponsor

Back
Top