Forum: PC-Programmierung Bitmanipulation Shiften


von Edwin (Gast)


Lesenswert?

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

von lalelu (Gast)


Lesenswert?


von Nop (Gast)


Lesenswert?

Edwin schrieb:

> was steht dann in der Variablen "i"?

Das ist implementationsabhängig, soweit es um C geht.

von Edwin (Gast)


Lesenswert?

Wie ist das gemeint, es ist Implementierungsabhängig? Wie kann ich das 
beeinflussen?

von Kaj (Gast)


Lesenswert?

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.

von Dr. Sommer (Gast)


Lesenswert?

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.

von Plauzi (Gast)


Lesenswert?

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.

von Dr. Sommer (Gast)


Lesenswert?

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

von (prx) A. K. (prx)


Lesenswert?

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.

: Bearbeitet durch User
von 1234567890 (Gast)


Lesenswert?

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.

von Dr. Sommer (Gast)


Lesenswert?

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.

von 1234567890 (Gast)


Lesenswert?

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.

von Dr. Sommer (Gast)


Lesenswert?

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.

von 1234567890 (Gast)


Lesenswert?

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.

von Dr. Sommer (Gast)


Lesenswert?

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 Anwendungen

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

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

GCC:
1
• Whether signed integer types are represented using sign and
2
  magnitude, two's complement, or one's complement, and whether the
3
  extraordinary value is a trap representation or an ordinary value
4
  (C99 and C11 6.2.6.2). 
5
6
GCC supports only two's complement integer types, and all bit patterns
7
are ordinary values.
1
• The results of some bitwise operations on signed integers
2
  (C90 6.3, C99 and C11 6.5). 
3
4
Bitwise operators act on the representation of the value including
5
both the sign and value bits, where the sign bit is considered
6
immediately above the highest-value value bit. Signed ‘>>’ acts on
7
negative numbers by sign extension.

Intern verwendet GCC "Arithmetic Shift Right", d.h. auf Backend-Ebene 
die ashr<mode>3 Pattern.

http://gcc.gnu.org/onlinedocs/gccint/Standard-Names.html#index-ashlm3-instruction-pattern

Das Verhalten ist also zumindest innerhalb von GCC portierbar. 
Beispielsweise erzeugt avr-gcc folgenden Code:
1
#include <stdint.h>
2
3
int8_t ashr1 (int8_t x)
4
{
5
    return x >> 1;
6
}
7
8
int8_t ashr7 (int8_t x)
9
{
10
    return x >> 7;
11
}
12
13
int8_t ashr_m128 (void)
14
{
15
    return -128 >> 4;
16
}
1
ashr1:
2
  asr r24
3
  ret
4
5
ashr7:
6
  lsl r24
7
  sbc r24,r24
8
  ret
9
10
ashr_m128:
11
  ldi r24,lo8(-8)
12
  ret

Also kein Anlass, die Inline-Assembler Keule auszupacken, wohl auch 
nicht bei anderen ernstzunehmenden Compilern.

von rbx (Gast)


Lesenswert?

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.

von Oliver S. (oliverso)


Lesenswert?

Die praktische Antwort auf diese theoretisch sicher interessante Frage 
lautet: egal.

Vorzeichenbehaftete Integer shiftet man einfach nicht.

Oliver

von Dr. Sommer (Gast)


Lesenswert?

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
unsigned char bytes [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
unsigned short res;
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_t res = (((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!

von A. S. (Gast)


Lesenswert?

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.

von Dr. Sommer (Gast)


Lesenswert?

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?

von u/i (Gast)


Lesenswert?

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.

von Dr. Sommer (Gast)


Lesenswert?

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.

von Dr. Sommer (Gast)


Lesenswert?

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.

Beitrag #5302663 wurde von einem Moderator gelöscht.
von Falk B. (falk)


Lesenswert?

@ Edwin (Gast)

>Ich habe eine Frage zum Shiften.

Shiften? Ist das sowas wie voten und liken?

AUA!!!!

von Dr. Sommer (Gast)


Lesenswert?

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.

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.