Das verwirrende char in C/C++



  • !rr!rr_. schrieb:

    Weder s,t,x noch y sind Zeiger

    intern schon. Für den Compiler sind diese Zeiger nur eben mit viel "syntaktischem Zucker" aufbereitet, der in Deinem Posting umfassend erläutert wird.

    Auch intern nicht.



  • krümelkacker schrieb:

    void foo() {
      double s[8] = {1,2,3};        /* #1 */
      double d[] = {5,6,7};         /* #2 */
      char x[] = {'H','e','l','o'}; /* #3 */
      char y[] = "Hello";           /* #4 */
    }
    

    #3: x ist ein 5-elementiges char-Array. Die in Hochkommas eingescholssenen

    #3: x ist ein 4-elementiges char-Array.



  • !rr!rr_. schrieb:

    krümelkacker schrieb:

    void foo() {
      double s[8] = {1,2,3};        // #1
      double t[] = {5,6,7};         // #2
      char x[] = {'H','e','l','o'}; // #3
      char y[] = "Hello";           // #4
    }
    

    [...]
    Weder s,t,x noch y sind Zeiger

    intern schon.

    s,t,x,y sind keine Zeiger. Punkt. Sich hier s,t,x,y als Zeiger vorzustellen führt nur zur Verwirrung bzw zu bösen Überraschungen.

    Wutz schrieb:

    #3: x ist ein 4-elementiges char-Array.

    Danke. Hatte das 'l' vergessen.



  • Ein grosses Lob an dich krümelkacker für die ausführliche Erklärung.
    Auch wenn es immer noch einige Zweifler gibt, denke ich, dass es für einen Großteil der Anfänger Licht ins Dunkel bringen wird.

    Mich verwirrt allerdings, dass du "abc" als lvalue-Ausdruck bezeichnest.

    ------------------

    P.S.:
    Nachtrag zu meinem Nachredner:

    wxSkip schrieb:

    krümelkacker schrieb:

    "abc" ist also ein ("Lvalue-Ausdruck" vom Typ) const char[4].

    Warum sind const-Variablen lvalues, wenn sie doch (ohne Cast) gar nicht auf der linken Seite einer Zuweisung stehen dürfen?

    lvalue heisst nicht, dass etwas auf der linken Seite des assignment-operators stehen darf.

    const int wert = 5;
    

    wert ist ein lvalue - kann aber nicht auf der linken Seite stehen. 😃



  • krümelkacker schrieb:

    "abc" ist also ein ("Lvalue-Ausdruck" vom Typ) const char[4].

    Warum sind const-Variablen lvalues, wenn sie doch (ohne Cast) gar nicht auf der linken Seite einer Zuweisung stehen dürfen?

    EDIT: 19 Sekunden zu spät 😃



  • Weil die Definition von 'lvalue' nicht davon abhängt, ob etwas auf der linken Seite einer Zuweisung stehen darf. Das mag die ursprüngliche Wortbildung gewesen sein. Man fährt aber wahrscheinlich besser, wenn man lvalue als 'location value' liest.



  • krümelkacker schrieb:

    s,t,x,y sind keine Zeiger. Punkt. Sich hier s,t,x,y als Zeiger vorzustellen führt nur zur Verwirrung bzw zu bösen Überraschungen.

    Ich glaube nicht, daß man C wirklich verstehen kann, ohne sich zu vergegenwärtigen, daß a[..] nunmal "syntaktischer Zucker" für Zeiger und Adreßarithmetik ist.

    Da ist doch nichts dabei - Zeiger und Adreßarithmetik laufen auf der CPU nunmal schneller, als ein "echter" Datentyp "array" laufen würde, der z.B. bei jedem Lese- und Schreib-Zugriff auf ein array-Element den Test auf Index-Grenzen durchführen würde.



  • inter2k3 schrieb:

    Mich verwirrt allerdings, dass du "abc" als lvalue-Ausdruck bezeichnest.

    Ich antworte mir mal selber.
    Wenn ich mir das recht überlege müsste das String-Literal / Zeichenketten-Literal "abc" tatsächlich ein lvalue sein.
    Grund dafür ist eben die von krümelkacker beschriebene Eigenschaft, dass das array implizit zu einem Pointer zerfällt (array-to-pointer-decay).

    Daher ist folgendes auch problemlos möglich:

    std::cout<<&"Hallo";
    

    Was lerne ich daraus? Erst denken - dann fragen 😃

    Nachtrag zum Vorredner:

    !rr!rr_. schrieb:

    Ich glaube nicht, daß man C wirklich verstehen kann, ohne sich zu vergegenwärtigen, daß a[..] nunmal "syntaktischer Zucker" für Zeiger und Adreßarithmetik ist.

    Fakt ist, dass es sich um arrays handelt und nicht um Zeiger. Soweit sind wir uns hoffentlich einig.
    Greifst du nun mit arrayname[index] auf ein Element zu, passiert tatsächlich nichts anderes, als besagter Zerfall zu einem pointer, gefolgt von pointer-arithmetik und danach die Indirektion. Da hast du natürlich absolut recht.

    const char name[] = "Hans";
    std::cout<<name[2];
    

    ist äquivalent zu

    const char name[] = "Hans";
    std::cout<<*(name+2);
    

    Trotzdem ist das Zeichenketten-Literal "Hans" vom Typ char const [5].



  • @rr_rr!-wie auch immer 😃
    Im Grunde genommen machst du ja auch mit Adressen rum, wenn du auf ganz normale Variablen zugreifst. Bloß halt nicht explizit, der Compiler macht das automatisch fü dich. Arrays liegen wie normale Variablen auf dem Stack und nicht wie bei dynamischen Arrays der Pointer aufs erste Element. Manchmal brauchst du aber diesen den Pointer, und den kannst du dir über den Stack-Pointer ausrechnen. Auch das macht der Compiler in bestimmten Fällen implizit automatisch, ansonsten rechnet er ihn dir immer noch bei explizitem Casting oder Addressenholen aus.



  • !rr!rr_. schrieb:

    Ich glaube nicht, daß man C wirklich verstehen kann, ohne sich zu vergegenwärtigen, daß a[..] nunmal "syntaktischer Zucker" für Zeiger und Adreßarithmetik ist.

    Genau andersrum. Man wird C nicht verstehen, wenn man das glaubt. Weil es schlicht und einfach nicht der Fall ist.

    Da ist doch nichts dabei - Zeiger und Adreßarithmetik laufen auf der CPU nunmal schneller, als ein "echter" Datentyp "array" laufen würde, der z.B. bei jedem Lese- und Schreib-Zugriff auf ein array-Element den Test auf Index-Grenzen durchführen würde.

    Das ist das, was mit "Array-to-Pointer-Decay" gemeint ist.

    Aber zuerst ist das Array da, und erst nach diesem "Zerfall" kann es als Pointer behandelt werden. Wenn man von vornherein annimmt, dass ein Array ein Pointer ist, wird man nicht in der Lage sein zu verstehen, was es mit Arrays vor dem Zerfall auf sich hat.



  • Bashar schrieb:

    !rr!rr_. schrieb:

    Ich glaube nicht, daß man C wirklich verstehen kann, ohne sich zu vergegenwärtigen, daß a[..] nunmal "syntaktischer Zucker" für Zeiger und Adreßarithmetik ist.

    Genau andersrum. Man wird C nicht verstehen, wenn man das glaubt. Weil es schlicht und einfach nicht der Fall ist.

    dann nenne doch mal eine Situation, in welcher mit einem Array hantiert wird, wobei nicht nur Zeiger + Adreßarithmetik im Spiel sind ?



  • @ !rr!rr_
    Nur weil man in vielen Situationen die implizite Umwandlung von Arrays zu Zeigern benutzt, heisst das noch lange nicht, dass es sich um dasselbe Konzept handelt. Arrays sind keine Zeiger. Darum sollte man sie sich auch nicht als solche vorstellen. Denn Situationen, wo sich beide Konzepte komplett verschieden verhalten, gibt es genug.

    C++ geht da sogar noch einen Schritt weiter und bietet Klassen an, die dem Grundkonzept Array (Speicherung einer Folge von Werten, ganz ohne Zeiger) näher kommen, aber intern genau gleich wie gewöhnliche C-Arrays arbeiten.



  • !rr!rr_. schrieb:

    dann nenne doch mal eine Situation, in welcher mit einem Array hantiert wird, wobei nicht nur Zeiger + Adreßarithmetik im Spiel sind ?

    Moment mal, du versuchst deine ursprüngliche Aussage abzuschwächen. Du hattest behauptet, ein Array wäre nichts weiter als "syntaktischer Zucker" für einen Pointer. Dass da auf Maschinenebene immer nur mit Adressen rumgerechnet wird hat niemand bestritten.

    Ich werde dir ein Beispiel geben, das zeigt, dass Arrays keine Pointer sind. Man sieht relativ häufig, dass Anfänger sowas versuchen:

    void f(int **p);
    
    int A[10][10];
    f(A);
    

    Hat ja Logik, wenn ein Array ein Pointer ist, dann muss ein 2D-Array ein Doppelpointer sein. Leider erzählt ihnen der Compiler was von nicht passenden Typen und sie fragen hier, wie man das casten muss. Was erzählst du ihnen?



  • Nexus schrieb:

    Arrays sind keine Zeiger.

    ja, wer behauptet denn das Gegenteil? 😮

    Ich sagte, Arrays seien "syntaktischer Zucker" für Zeiger und Adreßarithmetik. Intern (Maschinenebene) sind es dann letztendlich doch Zeiger, mit denen hantiert wird.



  • Arrays sind ein Speicherbereich, keine Zeiger.



  • !rr!rr_. schrieb:

    Nexus schrieb:

    Arrays sind keine Zeiger.

    ja, wer behauptet denn das Gegenteil? 😮

    Jemand, der unter dem gleichen Nick postet wie du.

    Weder s,t,x noch y sind Zeiger

    intern schon.



  • Edit: Bashar war schneller. 🙂



  • genug gelabert, werden wir mal konkret. Das da:

    #include<stdio.h>
    int main(){
      int i = 4;
      char a[10][10];
      a[0][4] = 'A';
      a[1][4] = 'B'; 
      a[2][4] = 'C';
      a[3][4] = 'D';
      printf("%c%c%c%c", a[0][i], a[1][i], a[2][i], a[3][i] );
      return(0); }
    

    kompiliert bei mir zu dem da:

    [...]
    	subq	$120, %rsp
    	movl	$4, -20(%rbp)
    	movb	$65, -124(%rbp)
    	movb	$66, -114(%rbp)
    	movb	$67, -104(%rbp)
    	movb	$68, -94(%rbp)
    	movl	-20(%rbp), %eax
    	cltq
    	movzbl	-98(%rbp,%rax), %eax
    	movsbl	%al,%esi
    	movl	-20(%rbp), %eax
    	cltq
    	movzbl	-108(%rbp,%rax), %eax
    	movsbl	%al,%ecx
    	movl	-20(%rbp), %eax
    	cltq
    	movzbl	-118(%rbp,%rax), %eax
    	movsbl	%al,%edx
    	movl	-20(%rbp), %eax
    	cltq
    	movzbl	-128(%rbp,%rax), %eax
    [...]
    

    Auffällig die beiden Folgen -94 -104 -114 -124 und -98 -108 -118 -128

    Quizfrage für die Ratefüchse: was fällt bei diesen zwei Folgen auf?

    ... ich weiß, ist etwas knifflig ...

    Machen wir's kurz:

    die Adress-Offsets zu a[j][4] haben die
    Form -(10*n+4) und folgen schön im Abstand 10 aufeinander. Der Zugriff erfolgt
    also über Z e i g e r und A d r e ß a r i t h m e t i k.



  • trotzdem ist ein Array kein Zeiger. Wenn der Zeiger zerstört wird, macht das dem Speicher nichts. Wenn das Array zerstört wird, ist der Speicherbereich freiwild.



  • Das ist nicht strittig. Der Arrayzugriff a[b] ist im Standard über Zeigerarithmetik definiert. Das ist tatsächlich nur syntaktischer Zucker.
    Es geht einzig um deine Behauptung, ein Array wäre ein Zeiger. Dass das nicht so ist, sieht man übrigens auch in deinem Assembler-Code, oder wo siehst du bei

    ; a[0][4] = 'A';
    movb    $65, -124(%rbp)
    

    eine Zeigerdereferenzierung? Da steht nur eine Adressberechnung relativ zu rbp, also relativ zu einer lokalen Variablen. Wäre a ein Zeiger, müsste der erst geladen und dereferenziert werden, bevor darauf 4 addiert werden.


Anmelden zum Antworten