Hallo zusammen,
Ich habe eine Frage zum Shiften. Nehmen wir mal an ich habe die eine
int8_t variable "i". Wenn "i" den Wert 2 enthält und ich shifte um 1
nach rechts, dann hat "i" den Wert 1. Soweit so gut, wenn ich nun den
Wert "-2" in "i" stehen habe und ebenfalls um eins nach rechts shifte,
was steht dann in der Variablen "i"?
Meine überlegung ist folgende.
i = -2 = 1111 1110
i >>= 1 => 0111 1111 = 127
Edwin schrieb:> Wie ist das gemeint, es ist Implementierungsabhängig?
Es haengt davon ab, wie es im Compiler implementiert ist. d.h.:
Mit Compiler A bekommst du Ergebnis X, und mit Compiler B kann das
Ergebnis ebenfalls X sein, es kann aber auch Y sein.
Edwin schrieb:> Wie kann ich das> beeinflussen?
Gar nicht.
Edwin schrieb:> Wie ist das gemeint, es ist Implementierungsabhängig?
Der C- bzw. C++-Standard definiert hier kein Verhalten sondern überlässt
es dem Compiler. Das heißt, je nach Compiler -Hersteller, -Version,
-Optionen und Plattform kann sich das Verhalten ändern. Daher lässt man
solche Dinge am Besten ganz bleiben. Was möchtest du denn erreichen?
Wahrscheinlich gibt es eine andere, wohldefinierte Möglichkeit ans Ziel
zu kommen.
Edwin schrieb:> Wie ist das gemeint, es ist Implementierungsabhängig?
ISO 9899:1999 6.5.7 Bit-wise shift operators §3
The integer promotions are performed on each of the operands. The type
of the result is that of the promoted left operand. If the value of the
right operand is negative or is greater than or equal to the width of
the promoted left operand, the behavior is undefined.
Plauzi schrieb:> If the value of the> right operand is negative or is greater than or equal to the width of> the promoted left operand, the behavior is undefined.
Das ist wohl nicht ganz die richtige Stelle... Das wäre wohl die hier,
insb. der letzte Satz:
"The result of E1 >> E2 is E1 right-shifted E2 bit positions. If E1 has
an unsigned type or if E1 has a signed type and a nonnegative value, the
value of the result is the integral part of the quotient of E1 / 2^E2 .
If E1 has a signed type and a negative value, the resulting value is
implementation-defined."
Plauzi schrieb:> ISO 9899:1999 6.5.7 Bit-wise shift operators §3
Falsche Stelle. Das Verhalten bei unpassender Anzahl Bits ist
undefiniert, nicht implementierungsabhängig.
Die richtige Stelle: The result of E1 >> E2 is E1 right-shifted E2 bit
positions. If E1 has an unsigned type or if E1 has a signed type and a
nonnegative value, the value of the result is the integral part of the
quotient of E1 / 2 E2 . If E1 has a signed type and a negative value,
the resulting value is implementation-defined.
ALlerdings ist eine Implementierung, die bei Typen mit Vorzeichen links
Nullen reinschiebt, sehr exotisch.
Wenn du definiertes Verhalten möchtest, mach die ein oder zwei Zeilen
für die Shiftoperation in Inline-Assembler. Da kannst du dir aussuchen
ob du ein arithmetisches shift, ein logisches shift oder ein rotate
nimmst. Vergiss nicht einen Kommentar in den Quelltext zu schreiben,
sonst wunderst du dich in einem halben Jahr, was du da gemacht hast.
1234567890 schrieb:> Wenn du definiertes Verhalten möchtest, mach die ein oder zwei Zeilen> für die Shiftoperation in Inline-Assembler.
Ist dann leider nur auf genau der einen Plattform definiert. Die
Inline-Assembler-Anweisung so korrekt zu benutzen dass sie auch bei
aktivierter Compiler-Optimierung funktioniert ist auch gar nicht so
einfach. Für so etwas Primitives wie eine Shift-Operation auf
Inline-Assembler zurückzugreifen ist schon sehr schlechter Stil. Da
sollte man sich lieber Gedanken machen wie man das korrekt in C macht,
z.B. indem man eine positive Zahl shiftet und dann nach signed castet.
Dr. Sommer schrieb:> Ist dann leider nur auf genau der einen Plattform definiert.
Ja, das ist ein ganz großer Nachteil.
Dr. Sommer schrieb:> Die> Inline-Assembler-Anweisung so korrekt zu benutzen dass sie auch bei> aktivierter Compiler-Optimierung funktioniert ist auch gar nicht so> einfach.
Naja, so schwierig ist das nicht, wenn man weiß was der Compiler macht.
Kenne deine Werkzeuge :-)
Dr. Sommer schrieb:> Für so etwas Primitives wie eine Shift-Operation auf> Inline-Assembler zurückzugreifen ist schon sehr schlechter Stil.
Das kommt drauf an. Es gibt einige Situationen, da lohnt sich sowas
richtig. Es kommt halt auf den Kontext an.
Dr. Sommer schrieb:> Da> sollte man sich lieber Gedanken machen wie man das korrekt in C macht,> z.B. indem man eine positive Zahl shiftet und dann nach signed castet.
Ja, sollte man sogar, wenn es portierbar sein soll.
Allerdings achtet man sehr oft darauf, dass der Code portierbar ist und
bleibt. 90% der geschriebenen Codes für kleine Mikrocontroller werden
aber selten bis nie in eine andere Familie portiert und innerhalb der
Familie funzt es mit Inline-Assembler sehr gut.
1234567890 schrieb:> Naja, so schwierig ist das nicht, wenn man weiß was der Compiler macht.
Es fängt schon damit an dass z.B. beim GCC für Cortex-M die Modifier für
die Register nicht dokumentiert sind und aus dem Internet
zusammengesucht werden müssen. Dann muss man genau die richtigen für die
jeweiligen Instruktionen nehmen, dafür sorgen dass sich
Input/Output-Register nicht überlappen falls beide gleichzeitig genutzt
werden usw. Was heute ohne Optimierungen funktioniert, klappt morgen mit
Optimierungen vielleicht unbemerkt nicht mehr weil der Compiler die
Register-Zuordnung geändert hat.
1234567890 schrieb:> Das kommt drauf an. Es gibt einige Situationen, da lohnt sich sowas> richtig. Es kommt halt auf den Kontext an.
Ja. Man sollte aber vorher genau wissen ob der Shift wirklich der
Flaschenhals ist. Und vieles können Compiler auch optimieren, obwohl es
in C kompliziert aussieht.
1234567890 schrieb:> 90% der geschriebenen Codes für kleine Mikrocontroller werden> aber selten bis nie in eine andere Familie portiert und innerhalb der> Familie funzt es mit Inline-Assembler sehr gut.
Allein schon das Wechseln des Compilers ist problematisch, weil die
Syntax für Inline-Assembly unterschiedlich ist. Das ist für mich ein
großes No-Go.
Dr. Sommer schrieb:> Was heute ohne Optimierungen funktioniert, klappt morgen mit> Optimierungen vielleicht unbemerkt nicht mehr weil der Compiler die> Register-Zuordnung geändert hat.Dr. Sommer schrieb:> Allein schon das Wechseln des Compilers ist problematisch, weil die> Syntax für Inline-Assembly unterschiedlich ist.
Innerhalb eines Produktzyklusses wechseln wir weder die Compiler noch
updaten wir sie. Die Toolchain bleibt bei einem Produkt von Anfang an
für das gesamte Team die selbe. Wir leben mit den Bugs. Sie stören uns
nicht, da wir sie kennen und umgehen. Nach einem Update oder Wechsel
fängt der ganze Lernzyklus von vorne an und solange der Lernzyklus
anhält kommen meist nur böse Überraschungen.
Update oder Wechsel kommt erst beim nächsten Produkt.
Bei sicherheitskritischen Anwendungen (Gefahr für Leib und Leben) oder
sehr energiesparenden Anwendungen mögen wir es nicht, wenn der Kunde mit
einem nicht funktioniernenden Produkt in 5 Jahren auf der Matte steht.
Wir haben hier ein paar Produkte, die schon seit einigen Jahren laufen
und immernoch weiterentwickelt werden: Und das mit einer hornalten
Toolchain. Am Anfang fand ich das alles lächerlich, aber mittlerweile
haben wir durch solche Updates auch schon einige Male ordentlich
Lehrgeld bezahlt.
Dr. Sommer schrieb:> Man sollte aber vorher genau wissen ob der Shift wirklich der> Flaschenhals ist.
Hab ja gesagt, dass es auf die Situation ankommt.
1234567890 schrieb:> Innerhalb eines Produktzyklusses wechseln wir weder die Compiler noch> updaten wir sie.
Das kann man aber nur innerhalb eines Teams so definieren... Wen man
Code weitergibt/verkauft, freut sich der Kunde nicht sehr wenn er nur
genau einen Compiler nutzen kann.
1234567890 schrieb:> aber mittlerweile> haben wir durch solche Updates auch schon einige Male ordentlich> Lehrgeld bezahlt.
Na, dann wird's Zeit für Standard-Konformen Code, der geht nicht
kaputt...
1234567890 schrieb:> Bei sicherheitskritischen Anwendungen1234567890 schrieb:> Wir leben mit den Bugs. Sie stören uns> nicht, da wir sie kennen und umgehen.
Bei sicherheitskritischen Anwendungen akzeptiert ihr Bugs? Das finde ich
irgendwie gewagt.
Johann L. schrieb:> Also kein Anlass, die Inline-Assembler Keule auszupacken,..http://www.c-jump.com/CIS77/ASM/Flags/F77_0160_sar_instruction.htm
mir geht es nur darum zu sagen, das Assembler eher mit Freiheit zu tun
hat.
Wäre schön, wenn so ein C-Compiler auch so etwas wie CPUID
( https://de.wikipedia.org/wiki/CPUID )
hätte bzw. auf einer schnell einzusehenden Karte angeben könnte, mit
welchen Tatsachen bei bestimmten Fragen zu rechnen ist.
Die Autoren von C selbst sagen auch nur, dass bei denen der Shift nach
rechts bei negativen Werten einer Division durch 2 entsprechen soll.
(oder eben -> Implementierungsfeinheit im Einzelfall)
Die (Intel-)Assemblerbefehle sagen: Du kannst beides machen, sowohl
Division (im mathematischen (?) Sinne als auch elegant rückwärts gehen,
wie auf z.B. einem Bankkonto.
rbx schrieb:> Wäre schön, wenn so ein C-Compiler auch so etwas wie CPUID> ( https://de.wikipedia.org/wiki/CPUID )> hätte
Tja, die Idee der meisten "höheren" Programmiersprachen ist, dass man
das gewünschte Verhalten auf einer abstrakten Ebene beschreibt, und der
Compiler dafür sorgt dass dieses auf der jeweiligen Plattform erreicht
wird. Es ist nicht so gedacht dass man explizit abfragt wie die
Plattform sich verhält, und sich das Programm daran anpasst. Das ist
durchaus unintuitiv und oft ärgert man sich, dass es nicht so geht wie
gewünscht, obwohl man genau weiß dass die konkrete Plattform das
gewünschte kann - aber anscheinend hat sich dieses Modell durchgesetzt.
In C und C++ hat man leider viele Elemente ohne definiertes Verhalten,
sodass man hier leicht Fehler machen kann, wie eben Shifts auf negativen
Zahlen.
Ein klassisches Beispiel für das Modell besteht in der
Endian-Konvertierung. Die direkte Herangehensweise ist es, die
Byte-Reihenfolge der Plattform abzufragen, ggf. die Bytes zu tauschen
und dann einen Integer daraus zu machen (wir gehen mal von char=8Bit,
short=16Bit aus):
1
unsignedcharbytes[2];
2
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
3
bytes[0]=highByte;
4
bytes[1]=lowByte;
5
#endif
6
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
7
bytes[1]=highByte;
8
bytes[0]=lowByte;
9
#endif
10
unsignedshortres;
11
memcpy(res,bytes,2);
Ist zwar kein CPUID aber eine andere Art des direkten Abfragens der
Eigenschaften der Zielplattform. Das _BYTE_ORDER_ -Makros ist aber
GCC-spezifisch - in Standard-C lässt sich so etwas gar nicht portabel
schreiben. Lustig wirds auch, wenn noch andere Reihenfolgen wie
PDP-Endian hinzukommen. Die Lösung auf Basis des abstrakten Modells
lautet:
1
uint16_tres=(((uint16_t)highByte)<<8)|lowByte;
Diese erscheint umständlich und es ist nicht so direkt klar was ein
Bitshift mit dem Tauschen von Bytes zu tun hat, und letztendlich
passiert in der Hardware genau das gleiche wie bei der ersten Variante.
Die 2. Variante ist aber die portable und zu bevorzugende - man passt
nicht das Programm an die Plattform an, sondern beschreibt abstrakt und
lässt den Compiler anpassen. Das ist einer der Haupt-Unterschiede
zwischen maschinennaher Programmierung (zB Assembler) und in
Hochsprachen (zB C).
Insbesondere bei Embedded-Architekturen klappt das natürlich nie
vollständig, und man muss oft auf plattformspezifische Dinge
zurückgreifen. Aber es erklärt, warum es in C kein CPUID gibt!
Dr. Sommer schrieb:> Na, dann wird's Zeit für Standard-Konformen Code, der geht nicht> kaputt...
Dein ganzer Beitrag suggeriert, dass du eher weniger mit Microcontroller
zu tun hast. Das findet sich aber in der Webadresse.
Achim S. schrieb:> Dein ganzer Beitrag suggeriert, dass du eher weniger mit Microcontroller> zu tun hast.
Doch, hab ich. Man kann auch für Controller guten Code schreiben. Nur
weil keiner die Innereien sieht ist das kein Argument das irgendwie zu
machen.
Achim S. schrieb:> Das findet sich aber in der Webadresse.
Welche jetzt?
A. K. schrieb:> Falsche Stelle. Das Verhalten bei unpassender Anzahl Bits ist> undefiniert, nicht implementierungsabhängig.
Natürlich ist das Verhalten implementierungsabhängig. Oder meinst du,
das bei Verwendung eines bestimmten Compilers beim Programmlauf mit
gleichen Eingangsbedingungen mal dies und mal das rauskommt?
Da die Bits einer Variablen nur zwei Zustände annehmen können, ist das
Ergebnis auch irgendwie definiert.
u/i schrieb:> Da die Bits einer Variablen nur zwei Zustände annehmen können, ist das> Ergebnis auch irgendwie definiert.
Du kennst den Unterschied zwischen "undefined" und "implementation
defined" nicht:
* Implementation defined heißt, dass das Rechenergebnis vom Standard
nicht vorgegeben ist, das Programm aber sonst normal weiterläuft
* Undefined Behaviour heißt, es darf irgendwas passieren, z.B.
Programmabsturz, Formatieren der Festplatte, Funktioniert wie erwartet,
Zeile wird übersprungen weil der Compiler annehmen darf, dass
undefiniertes Varhalten nie auftritt, ... Das ist logischerweise von der
Plattform, Compiler, genauen Umständen usw abhängig.
Shiften negativer Zahlen ist implementation defined, Shiften mit zu viel
Bits ist Undefined Behaviour. Zu letzterem gehören z.B. auch Dinge wie
Dereferenzieren eines Null-Pointers oder mancher(!) umgecasteter
Pointer, Signed Integer Overflow usw.
Hier noch die Quelle:
Im C-Standard, 3.4.1:
1 implementation-defined behavior
unspecified behavior where each implementation documents how the choice
is made
2 EXAMPLE An example of implementation-defined behavior is the
propagation of the high-order bit when a signed integer is shifted
right.
3.4.3
1 undefined behavior
behavior, upon use of a nonportable or erroneous program construct or of
erroneous data, for which this International Standard imposes no
requirements
2 NOTE Possible undefined behavior ranges from ignoring the situation
completely with unpredictable results, to behaving during translation or
program execution in a documented manner characteristic of the
environment (with or without the issuance of a diagnostic message), to
terminating a translation or execution (with the issuance of a
diagnostic message).
3 EXAMPLE
An example of undefined behavior is the behavior on integer overflow.
Heiner schrieb im Beitrag #5302663:
> Absoluter Unsinn. Kauf doch mal eine aktuelle CPU ohne Bugs!> -> gibt es nicht.
Ob man gezwungenermaßen Produkte mit Bugs kauft, oder man bekannte
behebbare Bugs im eigenen Code lässt, ist etwas völlig anderes.