Das verwirrende char in C/C++



  • Dravere schrieb:

    #include <iostream>
    
    bool isAnArrayAPointer()
    {
      return sizeof(int[10]) == sizeof(void*);
    }
    
    bool isTheNameOfAnArrayAPointer()
    {
      int temp[10];
      return sizeof(temp) == sizeof(void*);
    }
    
    int main()
    {
      std::cout << std::boolalpha << isAnArrayAPointer() << std::endl;
      std::cout << std::boolalpha << isTheNameOfAnArrayAPointer() << std::endl;
    }
    

    Frage beantwortet? 😉

    Grüssli

    Das macht doch 0 Sinn, mein Pointer verbraucht doch nicht mehr Speicher wenn er auf eine größere Datenstruktur zeigt, insofern trägt der Vergleich ob sizeof(array) == sizeof(pointer) Null zur Diskussion bei, egal welcher Meinung man auch sein mag.



  • gastantwort schrieb:

    Das macht doch 0 Sinn, mein Pointer verbraucht doch nicht mehr Speicher wenn er auf eine größere Datenstruktur zeigt, insofern trägt der Vergleich ob sizeof(array) == sizeof(pointer) Null zur Diskussion bei, egal welcher Meinung man auch sein mag.

    Es zeigt den Unterschied Array <-> Zeiger.
    Und das ein Array eben kein Zeiger ist.

    Beide Vergleiche sind btw unwahr.

    PS:
    Es geht hier nicht um Meinung. Dass ein Array kein Zeiger ist, ist einfach ein Fakt.



  • Shade Of Mine schrieb:

    Es zeigt den Unterschied Array <-> Zeiger.
    Und das ein Array eben kein Zeiger ist.

    Es zeigt eigentlich nur, dass es nicht exakt das gleiche ist. Dass der Unterschied fundamental und nicht nur eine willkürliche Definition ist zeigt es nicht. Mich würde man so jedenfalls nicht überzeugen können.



  • Bashar schrieb:

    Es zeigt eigentlich nur, dass es nicht exakt das gleiche ist. Dass der Unterschied fundamental und nicht nur eine willkürliche Definition ist zeigt es nicht. Mich würde man so jedenfalls nicht überzeugen können.

    Was würde für dich einen fundamentalen Unterschied bedeuten?



  • Warum sollte ein Array ein Zeiger sein? Da könnte auch eine Klasse ein Zeiger sein. Oder eine Referenz eine Klasse. :xmas2:



  • wxSkip schrieb:

    Eines interessiert mich jetzt aber doch noch. Wie soll alloca() funktionieren?

    Anhand der angeforderten Speichergröße wird der Platzbedarf auf dem Stack ermittelt, der Stackpointer entsprechend abgeändert und die Adresse auf dem Stack für den angeforderten Block übergeben.

    Exakt das gleiche passiert bei einem Array. Der einzige Vorteil bei einem Array ist, daß der Compiler weiß wie groß es ist. Das war's dann aber auch schon an Unterschieden.



  • huüh schrieb:

    Da könnte auch eine Klasse ein Zeiger sein.

    Eine Iterator-Klasse ist eine Abstraktion eines Zeigers.



  • ~john schrieb:

    wxSkip schrieb:

    Eines interessiert mich jetzt aber doch noch. Wie soll alloca() funktionieren?

    Anhand der angeforderten Speichergröße wird der Platzbedarf auf dem Stack ermittelt, der Stackpointer entsprechend abgeändert und die Adresse auf dem Stack für den angeforderten Block übergeben.

    Exakt das gleiche passiert bei einem Array. Der einzige Vorteil bei einem Array ist, daß der Compiler weiß wie groß es ist. Das war's dann aber auch schon an Unterschieden.

    🙄
    Ich habe doch unten genau hingeschrieben, was ich daran nicht verstehe, und das ist auch schon beantwortet worden...



  • ~john kapiert mal wieder nicht um was es geht.



  • ~john schrieb:

    huüh schrieb:

    Da könnte auch eine Klasse ein Zeiger sein.

    Eine Iterator-Klasse ist eine Abstraktion eines Zeigers.

    Lern lesen.



  • wxSkip schrieb:

    Ich habe doch unten genau hingeschrieben, was ich daran nicht verstehe, und das ist auch schon beantwortet worden...

    🙄
    Und mir war es wichtig diesen Punkt nochmals zu vertiefen. Es ist nun einmal so, daß viel zu viele nicht wissen, was wirklich in C und C++ mit simplen Arrays passiert. Zur Laufzeit unterscheidet sich ein Array gar nicht von einem Zeiger auf speziell allozierten Speicher. Der Compiler kann während der Übersetzung an Hand des Typs eines Arrays mit fester Größe dessen Größe anderen Orts wiederverwenden. Zur Laufzeit fehlen diese Informationen vollständig.

    Das Resultat dieses Unwissens ist es, daß es am laufenden Band Probleme mit Arrays gibt. Man muß sich nur die unzähligen buffer overflows wieder ins Gedächtnis rufen, die immer wieder zusammen gecodet werden.



  • ~john schrieb:

    Und mir war es wichtig diesen Punkt nochmals zu vertiefen. Es ist nun einmal so, daß viel zu viele nicht wissen, was wirklich in C und C++ mit simplen Arrays passiert. Zur Laufzeit unterscheidet sich ein Array gar nicht von einem Zeiger auf speziell allozierten Speicher. Der Compiler kann während der Übersetzung an Hand des Typs eines Arrays mit fester Größe dessen Größe anderen Orts wiederverwenden. Zur Laufzeit fehlen diese Informationen vollständig.

    Zur Laufzeit kannst du ja auch nicht sizeof(int) abfragen. Es gibt doch einen Unterschied, nämlich dass bei einem Array kein Pointer auf dem Stack liegt, sondern nur das Array selbst. Dessen Adresse kann nämlich durch den Stack-Pointer bestimmt werden, um dann in einen Pointer geschrieben zu werden. Das ist eine Verständnisfrage. Ein Array unterscheidet sich auch zur Laufzeit von einem Pointer auf speziell allozierten Speicher, da ein Array der speziell allozierte Speicher ist.
    Ganz nebenbei willst du meine Antwort korrigieren und dabei deine Meinung vertiefen, dabei gehst du gar nicht direkt auf sie ein. Was soll das?



  • ~john schrieb:

    Zur Laufzeit unterscheidet sich ein Array gar nicht von einem Zeiger auf speziell allozierten Speicher.

    Doch. Ein Zeiger ist eine Variable, die eine Adresse enthält. Wenn du dasselbe mit einem Array formulierst, dann gibt es diese Variable nicht, das Array ist einfach nur die Ansammlung seiner Elemente.

    So könnte man es sagen: Ein Array ist speziell allozierter Speicher. Aber ich glaube das weiß eh jeder.



  • Bashar wird es dir nicht langsam leid, die Fehler der anderen zu verbessern?



  • Jetzt sind es schon 8 Seiten Diskussion ´for notting´ - wie viele noch? 😕



  • so... erstmal cool das sich soviele gemeldet haben, auch wenn ich ab seite 2 kaum nocht irgendwas gefunden habe was mir auch nur im entferntesten klarheit ins dunkeln gebracht hat.. vielmehr hat mich das alles noch mehr verwirrt!

    ich versuche jetzt nochmal meinen aktuellen wissensstand darzulegen und hoffe das man mich dann entweder "nochmal" aufklärt, oder zustimmt. ps.: bitte widersprecht euch nicht alle immer wieder, das verwirrt ziemlich.. (was intern der compiler sieht etc.. ist mir relativ schnuppe, ich moechte nur wissen was ich wann benutze und zu welchem zweck)

    Also ich fang mal an:
    char a = 'a'; ist ein einzelnes Zeichen und sollte auch nur so zu initialisieren gehen
    const char a = 'a'; ist ein konstantes zeichen, welches sich später nicht mehr verändern lässt, es ist also read-only
    char[] ist ein Array/Feld vom Typ Char, mit anderen Worten ein Bereich in dem nur chars stehen.. z.B: 'H','e','l','l','o'
    Das ließe sich entweder über die Inidizies also mit a[0]='H' etc.. initialisieren, dann sollte man aber aufpassen, dass man die nullterminierung '\0' selbst mit einfuegt!

    wenn ich jetzt ein const char[] benutze, dann sollte auch das array diesmal nur read-only sein und ich könnte weder die adresse vom speicherbereich noch die werte verändern? <-- hier bin ich mir jetzt nicht sicher

    das char[] - Array kann sowohl statisch als auch dynamisch sein, statisch wäre es entweder wenn ich char[5] schreibe und muesste dann am besten ueber eine funktion, z.B. strcopy, eine zeichenkette von höchstens 4 buchstaben (wegen \0) zuweisen. (strcopy fuegt diese terminierung automatisch ein)

    ich kann das char[] - array auch "ausnahmsweise" mit = "Hello"; initialisieren, doch das wird dann implizit zu einem const char[5] (oder const char[6] ? ) umgewandelt?

    wenn ich jetzt am anfang ein char foo[3] array initialisiert habe, und merke zur laufzeit das reicht nicht aus, dann kann ich dem bereich erweitern in dem ich mit foo = new char[256]; den speicher dynamisch erweitere?

    so, das dürfte alles zu den char-arrays gewesen sein was mir jetzt einfaellt, nun zu den Zeigern!

    char * foo ist ein Pointer/Zeiger auf ein?! Char oder sogar auf eine "Folge" von Chars?
    Wieviel Speicherplatz oder besser, wieviele Zeichen kann ich jetzt in foo schreiben? rein theoretisch nur eines oder?

    Ich müsste dann also dynamisch zur laufzeit den speicherbereich mit new erweitern oder?

    wie kann ich dann werte in foo schreiben? über *foo = "hallo" sollte nicht funktionieren, da das ja eine konstante stringliteral ist bzw ein const char array und das sollte ja nicht mit pointer identisch sein.. könnte ich dann eventuell &"hello"; schreiben?

    oder kann ich einem char * nur ueber strcopy werte "direkt" zuweisen? also strcopy(foo, "hello"); sollte rein theoretisch funktionieren, die frage ist nur, ob ich SELBER vorher noch den speicherbereich erweitern muss oder ob das strcopy vllt sogar von alleine uebernimmt!

    jetzt noch abschließend die frage, koennte ich einem char* einen string zuweisen?

    So ich hoffe ihr könnt mir meine Fragen beantworten und das ichs halbwegs verstanden habe!



  • Invisible schrieb:

    Also ich fang mal an:
    char a = 'a'; ist ein einzelnes Zeichen und sollte auch nur so zu initialisieren gehen

    Ich verstehe nicht ganz, was mit der Initialisierungssache meinst.

    Invisible schrieb:

    const char a = 'a'; ist ein konstantes zeichen, welches sich später nicht mehr verändern lässt, es ist also read-only

    Richtig.

    Invisible schrieb:

    char[] ist ein Array/Feld vom Typ Char, mit anderen Worten ein Bereich in dem nur chars stehen.. z.B: 'H','e','l','l','o'
    Das ließe sich entweder über die Inidizies also mit a[0]='H' etc.. initialisieren, dann sollte man aber aufpassen, dass man die nullterminierung '\0' selbst mit einfuegt!

    Das lässt sich entweder gleich mit {'H', 'e', 'l', 'l', 'o'} initialisieren (um String-Funktionen darauf anwenden zu können, musst du aber noch '\0' dranhängen!) oder mit "Hello", bei dem die Nullterminierung schon dabei ist.

    Natürlich kannst du das auch im Nachhinein mit a[0]='H' initialisieren bzw. verändern.

    Invisible schrieb:

    wenn ich jetzt ein const char[] benutze, dann sollte auch das array diesmal nur read-only sein und ich könnte weder die adresse vom speicherbereich noch die werte verändern? <-- hier bin ich mir jetzt nicht sicher

    Die Werte kannst du im Nachhinein nicht mehr verändern (außer mit hässlichem, unsicheren Casten nach char[] oder char *). Die Adresse des Speicherbereichs eines Arrays lässt sich sowieso nicht ändern.

    Invisible schrieb:

    das char[] - Array kann sowohl statisch als auch dynamisch sein, statisch wäre es entweder wenn ich char[5] schreibe und muesste dann am besten ueber eine funktion, z.B. strcopy, eine zeichenkette von höchstens 4 buchstaben (wegen \0) zuweisen. (strcopy fuegt diese terminierung automatisch ein)

    Du meintest strcpy statt strcopy 😉 . Ansonsten richtig, falls du vorher schon weißt, was da reinkommt, immer statisch mit char test[] = "Hello" initialisieren.

    Invisible schrieb:

    ich kann das char[] - array auch "ausnahmsweise" mit = "Hello"; initialisieren, doch das wird dann implizit zu einem const char[5] (oder const char[6] ? ) umgewandelt?

    Nein, nicht ausnahmsweise, das ist besser so. Das wird auch nicht implizit umgewandelt, das ist schon ein const char[]-Array (siehe Diskussion auf den letzten 6 Seiten 😃 .

    Invisible schrieb:

    wenn ich jetzt am anfang ein char foo[3] array initialisiert habe, und merke zur laufzeit das reicht nicht aus, dann kann ich dem bereich erweitern in dem ich mit foo = new char[256]; den speicher dynamisch erweitere?

    Nein (das geht nur in Java), denn dein Array ist ein statischer Bereich auf dem Stack. Wenn du das brauchst, solltest du einen Pointer machen, der auf eine andere Adresse zeigen kann. Beispiel:

    C:

    //das jetzt in der main-Funktion
    char *dynamic_foo = malloc(3);
    if(dynamic_foo == NULL)
    {
        printf("Kein Speicher verfügbar\n");
        free(dynamic_foo);
        exit(1);
    }
    
    strcpy(dynamic_foo, "ab");
    
    //wenn du jetzt mehr brauchst
    if(need_more)
    {
        //Hier wird zuerst geschaut, ob nach dem aktuellen Speicherblock noch genügend Speicher verfügbar ist,
        //wenn ja, wird dieser einfach noch reserviert und dynamic_foo zeigt immer noch auf den gleichen Speicher
        //ansonsten zeigt dynamic_foo auf einen neuen Speicher, in den der alte String reinkopiert wurde
        char *new_mem = realloc(dynamic_foo, 256);
        if(new_mem == NULL)
        {
            printf("Kein Speicher verfügbar\n");
            free(dynamic_foo);  //alten Speicher trotzdem freigeben
            exit(1);
        }
        dynamic_foo = new_mem;  //dynamic_foo zeigt jetzt auf den neuen Speicher
    }
    
    //hier kommen noch Stringoperationen
    free(dynamic_foo);
    

    C++:

    try
    {
       char *dynamic_foo = new char[3];  //eigentlich sollte man natürlich std::string benutzen
       strcpy(dynamic_foo, "ab");
    
       if(need_more_memory)
       {
           //hier ist natürlich realloc() besser, aber trotzem als Beispiel
           char *new_mem = new char[256];
           strcpy(new_mem, dynamic_foo);
           delete[] dynamic_foo;
           dynamic_foo = new_mem;
       }
    
       delete[] dynamic_foo;
    }
    catch (std::bad_alloc err)
    {
        cout << "Speicher-Allokationsfehler: " << err.what() << "\n";
    }
    

    Invisible schrieb:

    so, das dürfte alles zu den char-arrays gewesen sein was mir jetzt einfaellt, nun zu den Zeigern!

    char * foo ist ein Pointer/Zeiger auf ein?! Char oder sogar auf eine "Folge" von Chars?

    Er zeigt auf ein char, du kannst aber einfach nach dem char weiterlesen und noch mehr chars dahinterschreiben, wenn du Speicher für mehr als einen char reserviert hast.

    Invisible schrieb:

    Wieviel Speicherplatz oder besser, wieviele Zeichen kann ich jetzt in foo schreiben? rein theoretisch nur eines oder?

    Erst mal gar keins, denn dein Zeiger zeigt am Anfang irgendwohin. Du musst Speicher mit malloc()/realloc()/new anfordern, bevor du reinschreiben darfst.

    Wichtig: mit "char *a = new char" reservierst du Speicher für einen char, den du nachher wieder mit "delete a" freigeben musst. Mit "char *a = new char[Menge]" reservierst du Speicher für <Menge> chars, den du wieder mit "delete[] a" freigeben musst.

    Invisible schrieb:

    Ich müsste dann also dynamisch zur laufzeit den speicherbereich mit new erweitern oder?

    Siehe oben.

    Invisible schrieb:

    wie kann ich dann werte in foo schreiben? über *foo = "hallo" sollte nicht funktionieren, da das ja eine konstante stringliteral ist bzw ein const char array und das sollte ja nicht mit pointer identisch sein.. könnte ich dann eventuell &"hello"; schreiben?

    "hallo" ist ein const char[]. Da Arrays jedoch implizit in Pointer konvertiert werden können, musst du kein & davorsetzen, du könntest es aber. Genauso könntest du auch &("Hello"[0]) schreiben.

    Hier zeigt dann dein const char * direkt auf den Speicher der Zeichenkettenkonstante "Hallo" im statischen Speicher deines Programms.

    Invisible schrieb:

    oder kann ich einem char * nur ueber strcopy werte "direkt" zuweisen? also strcopy(foo, "hello"); sollte rein theoretisch funktionieren, die frage ist nur, ob ich SELBER vorher noch den speicherbereich erweitern muss oder ob das strcopy vllt sogar von alleine uebernimmt!

    Nein, strcpy() macht nichts alleine. Du kannst mit ANSI C oder C++ auch nicht bestimmen, wie viel Speicher reserviert ist.
    Mit strcpy() werden alle chars des konstanten Zeichenarrays "hello" in den Speicher an und hinter der Adresse, auf die foo zeigt, kopiert.
    Es ist also nicht das gleiche wie oben, denn den Speicher, den du vorher alloziert haben solltest, darfst du auch wieder verändern!

    Invisible schrieb:

    jetzt noch abschließend die frage, koennte ich einem char* einen string zuweisen?

    Meinst du einen std::string?
    Das geht in C++ mit

    std::string foo = "Hello World";
    const char *foo_ptr = foo.c_str();  //Achtung: Das ist ein konstanter String. Willst du einen modifizierbaren String erhalten, musst du wieder mit dynamischer Speicherallozierung und strcpy() einen neuen C-String anfordern, der den Inhalt des anderen enthält.
    


  • Hey, vielen herzlichen Dank! Damit hast du mir gewaltig geholfen! Ein wenig unsicher bin ich noch, aber das kommt denke ich mit der Zeit 😉

    Ein weitere Frage noch:
    Ich kann ja maximal nen char Array von 256 Zeichen anlegen oder geht auch mehr?
    Wie ist das ganze bei char * foo; foo = new char[???]; Wieviel kann ich da anlegen?



  • Theoretisch kannst du bei beidem so viel machen, wie du willst. Praktisch ist bei einem char[] die Größe durch die verbleibende Stackgröße begrenzt und bei dynamischer Allozierung durch den größten freien Platz auf dem Heap (welcher erheblich größer sein sollte als der Platz auf dem Stack, außer eventuell, du machst deinen Stack riesig.
    Daher kannst du zur 100-Prozentigen Sicherheit die Speicherreservierung so überprüfen wie in meinem Beispiel. Auf Stackoverflow kannst du im Voraus AFAIK nicht prüfen (außer, du benutzt irgendwelche schmutzigen Tricks).



  • Hi,

    auch wenn die Diskussion um die (wie ich finde exzellente) Darstellung krümelkackers bereits weiter fortgeschritten ist, noch der Vollständigkeit halber eine Nachfrage:

    krümelkacker schrieb:

    ...

    struct array {
      int elemente[10];
    };
    
    array quelle();
    ...
    

    Sorry, aber ist das nicht die klassische "Funktionsdeklarationsfalle"?

    Gruß,

    Simon2.


Anmelden zum Antworten