Forum: Offtopic Was ist die Ausgabe?


von Til H. (turakar)


Lesenswert?

Ich habe hier mal ein kleines C Rätsel:
1
char* str = "abc";
2
str[2] = 'd';
3
printf("%s", str);

Was kommt dabei raus?

: Bearbeitet durch User
von Thomas M. (zumax) Benutzerseite


Lesenswert?

Vermutlich nen Segmentation Fault :)

Probiers mal so:
1
char str[] = "abc";
2
str[2] = 'd';
3
printf("%s", str);

Vielleicht möchte dies ein weiterer User mal erklären warum dies so ist.

Beste Grüße

von Til H. (turakar)


Lesenswert?

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.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

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

: Bearbeitet durch User
von Til H. (turakar)


Lesenswert?

Hättest du denn einen "wirklichen" Trick?

von Karl H. (kbuchegg)


Lesenswert?

Til Hoff schrieb:
> Hättest du denn einen "wirklichen" Trick?

Sicher. Google mal nach "Duffs Device"

von Sebastian W. (wangnick)


Lesenswert?

Til Hoff schrieb:
> Hättest du denn einen "wirklichen" Trick?

Klar, Til. War hier zwar schon im Forum, kennst Du aber vielleicht noch 
nicht:
1
unsigned char u8 = 0x88;
2
unsigned long u32 = u8<<8;

Was ist hiernach der Wert in u32?

LG, Sebastian

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Til Hoff schrieb:
> Ich habe hier mal ein kleines C Rätsel:
>
>
1
> char* str = "abc";
2
> str[2] = 'd';
3
> printf("%s", str);
4
>
>
> 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.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

...und hier die Ausgabe mit GCC:
1
til-hoff.c:2:1: warning: data definition has no type or storage class [enabled by default]
2
 str[2] = 'd';
3
 ^
4
til-hoff.c:2:1: warning: type defaults to 'int' in declaration of 'str' [-Wimplicit-int]
5
til-hoff.c:2:1: error: conflicting types for 'str'
6
til-hoff.c:1:7: note: previous definition of 'str' was here
7
 char* str = "abc";
8
       ^
9
til-hoff.c:2:1: error: invalid initializer
10
 str[2] = 'd';
11
 ^
12
til-hoff.c:3:8: error: expected declaration specifiers or '...' before string constant
13
 printf("%s", str);
14
        ^
15
til-hoff.c:3:14: error: expected declaration specifiers or '...' before 'str'
16
 printf("%s", str);
17
              ^

von Til H. (turakar)


Lesenswert?

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

und folglich:

: Bearbeitet durch User
von Til H. (turakar)


Lesenswert?

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

von Frank B. (f-baer)


Lesenswert?

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.
Der Typecast nach unsigned lonk (uint32_t) kommt erst mit der Zuweisung, 
zu dem Zeitpunkt ist aber das leftshift schon durchgeführt worden.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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.

von Yalu X. (yalu) (Moderator)


Lesenswert?

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

von Simon K. (simon) Benutzerseite


Lesenswert?

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

von Til H. (turakar)


Lesenswert?

Nun ja, die Online IDE, die ich getestet habe, hat 0x8800 bzw die 356... 
In Dezimalschreibweise ausgegeben.

--> https://ideone.com/SZbd1t

: Bearbeitet durch User
von Frank B. (f-baer)


Lesenswert?

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.

von Stefan R. (srand)


Lesenswert?

Frank Bär schrieb:
> Ahja? Was denn sonst? Ich lasse mich gern eines besseren belehren.

uint8_t hat acht Bit. char nicht unbedingt.

von Jürgen F. (snipor)


Lesenswert?

Schaut mal hier 
http://home.fhtw-berlin.de/~junghans/cref/CONCEPT/pointers.html
1
 main()
2
  {        
3
    char *colour="red";
4
    printf("%s \n",colour);  
5
  }

Was nutzt man eigentlich soetwas?
1
      main()
2
      {
3
        int *Width;
4
  *Width = 34;
5
      }

von Vn N. (wefwef_s)


Lesenswert?

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.

: Bearbeitet durch User
von Jürgen F. (snipor)


Lesenswert?

Ja aber der Pointer zeigt doch auf noch gar nichts, oder doch?
Also ich meine der Pointer ist nicht initialisiert.

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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.

von Vn N. (wefwef_s)


Lesenswert?

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

von Frank B. (f-baer)


Lesenswert?

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.

von Frank B. (f-baer)


Lesenswert?

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.

von Vn N. (wefwef_s)


Lesenswert?

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.

von Frank B. (f-baer)


Lesenswert?

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

von Sebastian W. (wangnick)


Lesenswert?

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

von Yalu X. (yalu) (Moderator)


Lesenswert?

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
unsigned char u8 = 0x88;
2
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.

Folgendes ist aber trotz des negativen linken Operanden von << in
Ordnung:
1
signed char s8 = -0x44;
2
signed long s32 = 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.

: Bearbeitet durch Moderator
von Vn N. (wefwef_s)


Lesenswert?

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.

von Sebastian W. (wangnick)


Lesenswert?

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

von Paul B. (paul_baumann)


Lesenswert?

Ihr müßt Euch alle mal ein gutes C-Buch kaufen, damit die Resultate
nicht so verschieden ausfallen.
;-)
SCNR
Paul

von Yalu X. (yalu) (Moderator)


Lesenswert?

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.

von Til H. (turakar)


Lesenswert?

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

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.