Das ist schon mal richtig und mit dem Array geht es auch. Nun ist es
Aufgabe des Lesers, hersuszufinden, wieso.
Und falls ihr noch weitere solche Tricks habt, nur raus damit.
Til Hoff schrieb:> Und falls ihr noch weitere solche Tricks habt, nur raus damit.
Trick?
Du beliebst zu scherzen.
Lies ein C-Buch. Da steht das alles drinnen. Auch warum du deine erste
Version nicht machen darfst.
<Kopfschüttel>
heutzutage ist es also schon ein Trick, wenn man sein Handwerk gelernt
hat. Was kommt als nächstes? Bauarbeiter die sich in 2 Abendschulungen
zum Chirurgen umschulen lassen? Dolmetscher mit Sprachkenntnissen, die
sie gerade mal dazu befähigen bei McDonalds etwas zu bestellen? ...
>> Was kommt dabei raus?
1) Es ist kein gültiges C-Programm. Z.B. Definiert die erste Zeile
str als char*, zdie zweite Zeile definiert str als int[]. Die
zweite Zeile ist keine Zuweisung, da außerhalb einer Funktion.
Die dritte Zeile fängt an wie eine Funktionsdefnition / -deklaration
mit implizitem Rückgabewert int.
2) Selbst wenn man es syntaktisch zu einem C-Programm macht,
ist die 1. Zeile laut C-Standard undefiniert. Früher kannte GCC
mal -fwritable-strings o.ä., aber das gibt es schon lange nicht mehr.
> 1) Es ist kein gültiges C-Programm. Z.B. Definiert die erste Zeile> str als char*, zdie zweite Zeile definiert str als int[]. Die> zweite Zeile ist keine Zuweisung, da außerhalb einer Funktion.> Die dritte Zeile fängt an wie eine Funktionsdefnition / -deklaration> mit implizitem Rückgabewert int.
Ist das nötig? nIch denke mal jeder hat verstanden, dass man sich int
main() etc denken soll. Entsprechende Includes wie die Standardausgabe
natürlich auch.
Zu der Frage mit dem leftshift:
> Sicher. Google mal nach "Duffs Device"
Das ist cool. Weißt du denn wie es mit der Optimierung bei CC steht?
Laut der Wiki ist ein Performanceboost bei guten Compilern wohl nicht zu
erwarten (speziell interessiert mich arm none eabi, wenn du den kennst).
Falsch. Es kommt 0 raus.
Ein char (uint8_t) wird um 8 Stellen nach links geschoben, danach ist es
leer.
Der Typecast nach unsigned lonk (uint32_t) kommt erst mit der Zuweisung,
zu dem Zeitpunkt ist aber das leftshift schon durchgeführt worden.
Auch falsch.
Das Ergebnis hängt ab von der Größe von int.
* 32-Bit int: es kommt 0x8800 raus.
* 16-Bit int und 8-Bit char: es kommt 0xffff8800 raus.
* 16-Bit int und 16-Bit char: es kommt 0x8800 raus.
Johann L. schrieb:> * 32-Bit int: es kommt 0x8800 raus.> * 16-Bit int und 8-Bit char: es kommt 0xffff8800 raus.> * 16-Bit int und 16-Bit char: es kommt 0x8800 raus.
Immer noch nicht ganz richtig:
* 16-Bit int und 8-Bit char: undefined
Frank Bär schrieb:> Til Hoff schrieb:>> Zu der Frage mit dem leftshift:>>>>>>> und folglich:>>>>> Falsch. Es kommt 0 raus.>> Ein char (uint8_t) wird um 8 Stellen nach links geschoben, danach ist es> leer.
char != uint8_t
> Der Typecast nach unsigned lonk (uint32_t) kommt erst mit der Zuweisung,> zu dem Zeitpunkt ist aber das leftshift schon durchgeführt worden.
unsigned long ist nur bedingt gleich uint32_t
Simon K. schrieb:> Frank Bär schrieb:>> Til Hoff schrieb:>>> Zu der Frage mit dem leftshift:>>>>>>>>>> und folglich:>>>>>>>> Falsch. Es kommt 0 raus.>>>> Ein char (uint8_t) wird um 8 Stellen nach links geschoben, danach ist es>> leer.> char != uint8_t
Ahja? Was denn sonst? Ich lasse mich gern eines besseren belehren.
Aber nur mal so nebenher: Dieser ganze Mist von wegen 32bit-dies und
16bit-jenes ist der Grund, warum man diese Datentypen nicht benutzen
sollte. int, char, long sind nicht sauber definiert.
Vernünftige und vor allem Compiler-unabhängige Datentypen gibts nur aus
der stdint.h. Alles andere ist ein Ratespiel und damit unsauber.
16Bit-Compiler sehen 32 Bit als long, 32bit-Compiler finden das normal
und arbeiten bei long mit 64 Bit. Was float und double angeht, wird es
richtig finster, da geht es munter zwischen 16 und 64 Bit hin und her.
>> Der Typecast nach unsigned lonk (uint32_t) kommt erst mit der Zuweisung,>> zu dem Zeitpunkt ist aber das leftshift schon durchgeführt worden.> unsigned long ist nur bedingt gleich uint32_t
Siehe rant oben.
Frank Bär schrieb:> Ahja? Was denn sonst? Ich lasse mich gern eines besseren belehren.
Erstmal ist uint8_t explizit unsigned, während char entweder signed oder
unsigned sein kann. uint8_t ist also höchstens unsigned char, aber nicht
char. Desweiteren gibt es Prozessoren, die gar nicht auf Byteebene
addressieren können, dort existiert uint8_t schlichtweg nicht. Char
hingegen ist immer die kleinste adressierbare Einheit, bei den C2k DSPs
von TI z.B. 16 Bit (was zu tollen Effekten führt, wenn man Code
portiert, der sich darauf verlässt, dass ein Char 8 Bit hat).
Jürgen F. schrieb:> Was nutzt man eigentlich soetwas? main()> {> int *Width;> *Width = 34;> }
Ach komm schon, in jedem noch so schlechten C Tutorial werden Pointer
erklärt.
Frank Bär schrieb:> Simon K. schrieb:>> Frank Bär schrieb:>>>>>> Falsch. Es kommt 0 raus.>>>>>> Ein char (uint8_t) wird um 8 Stellen nach links geschoben, danach ist
Es wird eben kein char (und auch kein uint8_t) geschoben, sondern ein
int. Vor dem Schieben wird der uint8_t zum int promoted. Dann wird
geschoben, mit undefiniertem Verhalten falls der zu schiebende Wert dann
negativ ist, wie Yalu bemerkte. Erst dann wird das Ergebnis zu einem
uint32_t gemacht.
> Aber nur mal so nebenher: Dieser ganze Mist von wegen 32bit-dies und> 16bit-jenes ist der Grund, warum man diese Datentypen nicht benutzen> sollte. int, char, long sind nicht sauber definiert.
stdint.h hilft aber auch nicht, wenn es um integer-promotion rules geht.
Ein int bleibt ein int, und die promotion-Rules sind auch in C99 / C11
die gleichen wie in C89.
Richtig. Deshalb nutzt man es ja auch nicht. Uninitialisierte Pointer
sind undefiniert. Je nach Zielplattform gibt es eine Fehlermeldung zur
Laufzeit (z.B. unter Windows), oder du schreibst irgendwo im Speicher
rum (µC), was entweder zu einem korrupten RAM führt (wenn im Pointer
zufällig was sinnvolles steht) oder gleich einen Fault auslöst (wenn du
auf eine unzulässige Addresse schreiben willst). Natürlich kann der
Compiler solche Dinge auch nach belieben wegoptimieren.
Es kann aber auch zu sowas führen:
http://static3.wikia.nocookie.net/__cb20130416015541/powerlisting/images/9/93/Divded_by_zero_6848.jpg
vn nn schrieb:> Erstmal ist uint8_t explizit unsigned, während char entweder signed oder> unsigned sein kann. uint8_t ist also höchstens unsigned char, aber nicht> char. Desweiteren gibt es Prozessoren, die gar nicht auf Byteebene> addressieren können, dort existiert uint8_t schlichtweg nicht. Char> hingegen ist immer die kleinste adressierbare Einheit, bei den C2k DSPs> von TI z.B. 16 Bit (was zu tollen Effekten führt, wenn man Code> portiert, der sich darauf verlässt, dass ein Char 8 Bit hat).
Der nächste Grund, warum man char meiden sollte. Spätestens, wenn man
irgendwelche Ergebnisse extern speichern will, geht der Arsch auf
Grundeis.
Da ist man nunmal auf die Bitrepräsentation angewiesen, und kann nicht
dem Compiler die Auswahl überlassen, was er für richtig hält.
Und bezogen auf die 16bit-Adressierung - es gibt mit an Sicherheit
grenzender Wahrscheinlichkeit eine Möglichkeit für den Compiler
(sicherlich ineffizient), trotzdem eine 8Bit-Adressierung vorzunehmen.
Aber entscheidend ist an der Stelle doch, dass man sich eben NICHT davon
abhängig macht, wie der Compiler bzw. der Prozessor gerade gelaunt oder
gestrickt sind. uint8_t ist uint8_t, die Bitrepräsentation ist über alle
Plattformen und Compiler gleich. Eine Funktion, die solche Datentypen
verwendet, ist mit kleinsten bzw. gänzlich ohne Anpassung portierbar.
Ich hatte mal die Freude, ein 15k-Zeilen-Programm von einem 16bit- auf
einen 32-Bit-Prozessor zu portieren. Dabei auch noch mit
Compilerwechsel. Die erste Maßnahme war die Eliminierung uneindeutiger
Datentypen.
Solange man die Daten nur intern weiterverwendet, mag das noch gehen,
aber sobald man eine beliebige Kommunikationsschnittstelle bedienen
möchte, sind solche Datentypen kontraproduktiv.
Johann L. schrieb:>> Aber nur mal so nebenher: Dieser ganze Mist von wegen 32bit-dies und>> 16bit-jenes ist der Grund, warum man diese Datentypen nicht benutzen>> sollte. int, char, long sind nicht sauber definiert.>> stdint.h hilft aber auch nicht, wenn es um integer-promotion rules geht.> Ein int bleibt ein int, und die promotion-Rules sind auch in C99 / C11> die gleichen wie in C89.
Meine Einlassung bzgl. stdint.h bezog sich weniger auf
Compiler-Arithmetik an sich, als auf die Unzahl von Möglichkeiten, die
sich durch die Uneindeutigkeit von sog. Standard-Datentypen (die
heutzutage auch noch als verwendenswert gelehrt werden) ergeben.
Aber danke, das wusste ich tatsächlich nicht. Die Definition "smaller
than int" ist nun eigentlich eine noch größere Dummheit, aber das
scheint wohl der Lauf der Dinge. Ein C99-konformer 8Bit-Compiler würde
also entweder int als 8Bit-Datentyp interpretieren oder, noch schlimmer,
jede Rechnung in 16 Bit durchführen. Gut, dass es das nicht gibt.
Frank Bär schrieb:> Und bezogen auf die 16bit-Adressierung - es gibt mit an Sicherheit> grenzender Wahrscheinlichkeit eine Möglichkeit für den Compiler> (sicherlich ineffizient), trotzdem eine 8Bit-Adressierung vorzunehmen.
Der vollständigkeit halber: nein.
Jürgen F. schrieb im Beitrag #3523964:
> Schön dass auf einer Hochschulseite solche Beispiele gezeigt werden, ist> bestimmt förderlich um Verwirrung zu vermeiden.
Dort steht auch sonst jede Menge Mist.
vn nn schrieb:> Frank Bär schrieb:>> Und bezogen auf die 16bit-Adressierung - es gibt mit an Sicherheit>> grenzender Wahrscheinlichkeit eine Möglichkeit für den Compiler>> (sicherlich ineffizient), trotzdem eine 8Bit-Adressierung vorzunehmen.>> Der vollständigkeit halber: nein.
Der Vollständigkeit halber: doch. 2 Minuten danach suchen spuckt
__byte() aus.
Hier ist ein Beispiel dazu:
http://processors.wiki.ti.com/index.php/Byte_Accesses_with_the_C28x_CPU#Using_the_byte_Intrinsic
Nicht schön, aber damit funktioniert dann auch 8Bit-Arithmetik.
Würde mich auch schwer gewundert haben...
Johann L. schrieb:> Es wird eben kein char (und auch kein uint8_t) geschoben, sondern ein> int. Vor dem Schieben wird der uint8_t zum int promoted. Dann wird> geschoben, mit undefiniertem Verhalten falls der zu schiebende Wert dann> negativ ist, wie Yalu bemerkte. Erst dann wird das Ergebnis zu einem> uint32_t gemacht.>> stdint.h hilft aber auch nicht, wenn es um integer-promotion rules geht.> Ein int bleibt ein int, und die promotion-Rules sind auch in C99 / C11> die gleichen wie in C89.
Ok, mein kleines Rätsel ist also geknackt. Herzlichen Glückwunsch,
Johann und Yalu! Insbesonders der Hinweis von Yalu, das << von negativen
Zahlen im C-Standard als undefiniert spezififiert ist, war mir neu!
Siehe auch
http://stackoverflow.com/questions/3668734/unsigned-and-signed-values-in-c-what-is-the-output.
Der OP wird allerdings wohl leicht erschüttert ob der Entwicklung seines
Threads sein :) :) :)
LG, Sebastian
Sebastian Wangnick schrieb:> Insbesonders der Hinweis von Yalu, das << von negativen> Zahlen im C-Standard als undefiniert spezififiert ist, war mir neu!
Das hast du falsch verstanden, denn du darfst prinzipiell auch negative
Zahlen nach links shiften. Der Standard sagt folgendes:
"The result of E1 << E2 is E1 left-shifted E2 bit positions; vacated
bits are filled with zeros. If E1 has an unsigned type, the value of
the result is E1 × 2**E2, reduced modulo one more than the maximum
value representable in the result type. If E1 has a signed type and
nonnegative value, and E1 × 2**E2 is representable in the result type,
then that is the resulting value; otherwise, the behavior is
undefined."
Im obigen Beispiel
1
unsignedcharu8=0x88;
2
unsignedlongu32=u8<<8;
ist 0x88 eine positive Zahl, die nach den Integer-Promotion-Regeln in
ein int konvertiert wird. Das Ergebnis von u8 << 8 ist daher ebenfalls
vom Typ int. Laut Standard soll 0x88 << 8 = 0x88 * 2**8 = 0x8800 sein.
Ist int nur 16 Bit breit, ist der maximal darstellbare Wert 0x7fff,
somit kann 0x8800 nicht als int dargestellt werden, und das Verhalten
ist undefiniert.
Folgendes ist aber trotz des negativen linken Operanden von << in
Ordnung:
1
signedchars8=-0x44;
2
signedlongs32=s8<<8;
Das Ergebnis -0x4400 ist auch als 16-Bit-Integerzahl darstellbar,
weswegen hier keine Probleme auftreten.
Edit:
Alles, was ich in diesem Beitrag über negative Operanden geschrieben
habe, hat sich als falsch herausgestellt (s. Beitrag von Sebastian
Wangnick vom 08.02.2014 um 11:19).
Richtig ist:
Left-Shifts von negativen Zahlen sind immer undefined.
Frank Bär schrieb:> Der Vollständigkeit halber: doch. 2 Minuten danach suchen spuckt> __byte() aus.> Hier ist ein Beispiel dazu:> http://processors.wiki.ti.com/index.php/Byte_Accesses_with_the_C28x_CPU#Using_the_byte_Intrinsic> Nicht schön, aber damit funktioniert dann auch 8Bit-Arithmetik.> Würde mich auch schwer gewundert haben...
In Ordnung, du sollst recht haben, hab ich tatsächlich nicht gewusst. Da
ich bisher keinen Bedarf danach hatte, hab ich mich auf Aussagen dritter
verlassen.
Yalu X. schrieb:> Das hast du falsch verstanden, denn du darfst prinzipiell auch negative> Zahlen nach links shiften. Der Standard sagt folgendes:>> "The result of E1 << E2 is E1 left-shifted E2 bit positions; vacated> bits are filled with zeros. If E1 has an unsigned type, the value of> the result is E1 × 2**E2, reduced modulo one more than the maximum> value representable in the result type. If E1 has a signed type and> nonnegative value, and E1 × 2**E2 is representable in the result type,> then that is the resulting value; otherwise, the behavior is> undefined.">> Im obigen Beispiel> unsigned char u8 = 0x88;> unsigned long u32 = u8<<8;>> ist 0x88 eine positive Zahl, die nach den Integer-Promotion-Regeln in> ein int konvertiert wird. Das Ergebnis von u8 << 8 ist daher ebenfalls> vom Typ int. Laut Standard soll 0x88 << 8 = 0x88 * 2**8 = 0x8800 sein.> Ist int nur 16 Bit breit, ist der maximal darstellbare Wert 0x7fff,> somit kann 0x8800 nicht als int dargestellt werden, und das Verhalten> ist undefiniert.
Moment. "If E1 has an unsigned type, [...]. If E1 has a signed type and
nonnegative value, [...]; otherwise, the behavior is undefined."
Danach ist das Verhalten für alle negativen Zahlen (also "signed type"
und nicht "nonnegative value") als undefiniert spezifiziert, oder?
Oder meint der Standardtext, dass das Verhalten genau dann "undefined"
ist, wenn sowohl "E1 has a signed type and nonnegative value" und dann
auch das Ergebnis nicht repräsentierbar ist? In diesem Fall wäre dann
aber das Verhalten für negative Zahlen (also "signed type" und eben
nicht "nonnegative value") dadurch gar nicht erfasst? Und gilt dann
einfach die erste Klausel "The result of E1 << E2 is E1 left-shifted E2
bit positions; vacated bits are filled with zeros.", und das Resultat
ist arithmetisch gar nicht spezifiziert und damit
repräsentationsabhängig?
LG, Sebastian
Sebastian Wangnick schrieb:> Danach ist das Verhalten für alle negativen Zahlen (also "signed type"> und nicht "nonnegative value") als undefiniert spezifiziert, oder?
Ja, du hast völlig recht. Ich habe gestern in
"If E1 has a signed type and nonnegative value, and E1 × 2**E2 is
representable in the result type, then that is the resulting value;
otherwise, the behavior is undefined."
das "and nonnegative value" überlesen. Danke für den Hinweis.
Ich hatte für ein paar Tage mal keinen PC und schon ist der Thread hier
total überfüllt :D
Ist natürlich genial, dass sich so etwas aus meiner Frage entwickelt
hat. "Duffs device" ist übrigens auch spannend, aber anscheinend ja
nicht mehr "gut", d.h. kein Element der Leistungsoptimierung mehr (dank
der Compiler):
http://de.wikipedia.org/wiki/Duff%E2%80%99s_Device