Programm stürzt bei c-strings ab



  • Hallo,

    Ich hab hier einen Quellcode, dessen Funktion leerzeichenEntferner aus meinem Lehrbuch stammt.

    #include <iostream>
    using namespace std;
    
    void leerzeichenEntfernen(char *s) {
    	char *q = s;
    	do {
    		if(*s != ' ') {
    			*q++ = *s;
    		}
    	} while(*s++);
    }
    
    int main() {
    	char *quelle = "a bb   ccc d";
    	leerzeichenEntfernen(quelle);
    	cout << quelle;
    }
    

    Ich finde den Fehler nicht, wieso stürzt das Programm ab?

    Ich benutze den GCC 4.2.1 in der IDE Code::Blocks auf Windows 7 Home Premium 64 bit.



  • char *quelle = "a bb   ccc d";
    

    Das solltest du eigentlich wenn überhaupt so deklarieren:

    const char *quelle = "a bb   ccc d";
    

    Du hast da nämlich ein String Literal, was aber (wie jedes Literal) konstant ist und sich zur Laufzeit nicht ändert. Wenn du es trotzdem versuchst, dann endet das (sehr wahrscheinlich; da undefiniert) in einem Laufzeitfehler.



  • Wenn ich es nach const char umändern würde, dann müsste ich ja einiges umschreiben. Aber per
    char quelle[] = "a bb ccc d";
    funktionierts - warum?
    Und wo liegt überhaupt der Unterschied zwischen den beiden Versionen char *quelle und char quelle[]?



  • Weil das ein Array von char's ist, wogegen das andere ein Zeiger auf ein char ist.

    Bei dem hier:

    char quelle[] = "a bb ccc d";
    

    macht der Compiler eigentlich das hier:

    char quelle[11] = {'a',' ','b','b',' ','c','c','c',' ', 'd', '\0'};
    

    Er legt also den Speicher für den String (oder besser ein ganz normales Array von chars) an und kopiert die Zeichen da rein. Bei der ersten Version allerdings zeigt der Zeiger lediglich auf das erste Element des Literals (welches lediglich lesbar und nicht beschreibbar ist). Beachte, dass die Nullterminierung ebenfalls mit rein kommt, weil diese implizit in einem String von der Form "asfd" dabei ist.

    Der Unterschied sollte jetzt klar sein. Wieder einmal ein Argument warum Zeiger und Arrays eben nicht gleich sind (eine Behauptung, die immer mal wieder aufkommt). 😉



  • Okay danke, das hab ich jetzt verstanden 🙂
    Also funktioniert es bei einem Array weil wir hier einen Speicherbereich haben den wir verändern dürfen und aus char *s(dem Parameter) aufgrund der Ähnlickeit von Zeigern und Arrays ein Array wird?



  • Es ist einfach eine speziellere Art einer Arrayinitialisierung. Aber prinzipiell hast dus denke ich jetzt verstanden.

    Du kannst ja mal mit dem sizeof Operator schauen wie gross die Array und wie gross die Zeiger Variante ist. Dann siehst du, dass das eine halt eben grösser ist und das andere nur ein Zeiger.



  • Gut, also so weit hab ich das jetzt verstanden:

    Bei der Zeiger-Variante haben wir lediglich einen Zeiger, der auf ein Zeichenliteral zeigt. Das Zeichenliteral ist aber konstant, und wenn ich trotzdem versuche es zu verändern (in diesem Fall mit der Zuweisung) dann ist das Verhalten undefiniert und das Programm stürzt ab, (wie bei der Teilung durch 0?).
    Bei der Array-Variante haben wir aber einen festen Speicher wo das Literal "drin" ist, d.h. wir können damit machen was wir wollen und den Speicher nach belieben verändern.
    Ist das so richtig? 😉
    Ach ja: Eins versteh ich trotzdem nicht. Wenn ich einen Zeiger als formalen Parameter habe und dann die Funktion mit einem Array aufrufe - wird dann aus dem Zeiger als Parameter ein Array? Oder wie hab ich das zu verstehen?



  • Incocnito schrieb:

    dann ist das Verhalten undefiniert und das Programm stürzt ab, (wie bei der Teilung durch 0?).

    Undefiniertes Verhalten heißt nicht unbedingt Absturz. Und ein undefiniertes Verhalten (Zuweisung an eigentlich konstantes char) ist nicht gleich dem anderen undefinierten Verhalten (Division durch 0).
    Undefiniertes Verhalten heißt "alles kann passieren" - z.B. dass der Rechner bei der Zuweisung an char-Literale laut piept und bei der Division durch 0 explodiert. (eine Plattform und ein Compiler die das hinbekommen wären zwar ungewöhnlich, in der Hinsicht aber dennoch standardkonform).

    Incocnito schrieb:

    Bei der Array-Variante haben wir aber einen festen Speicher wo das Literal "drin" ist, d.h. wir können damit machen was wir wollen und den Speicher nach belieben verändern.

    Bei der Zuweisung an ein Array wird implizit der Inhalt des char*-Literals in das (veränderliche) Array kopiert.

    Ach ja: Eins versteh ich trotzdem nicht. Wenn ich einen Zeiger als formalen Parameter habe und dann die Funktion mit einem Array aufrufe - wird dann aus dem Zeiger als Parameter ein Array? Oder wie hab ich das zu verstehen?

    Andersrum. Das nennt sich "array-to-pointer-decay" - bei der Übergabe des Arrays wird stattdessen ein Pointer auf das erste Element übergeben.



  • pumuckl schrieb:

    Ach ja: Eins versteh ich trotzdem nicht. Wenn ich einen Zeiger als formalen Parameter habe und dann die Funktion mit einem Array aufrufe - wird dann aus dem Zeiger als Parameter ein Array? Oder wie hab ich das zu verstehen?

    Andersrum. Das nennt sich "array-to-pointer-decay" - bei der Übergabe des Arrays wird stattdessen ein Pointer auf das erste Element übergeben.

    Dazu gabs schon einen sehr informativen thread, in dem unter anderem von krümelkacker ein guter Beitrag geschrieben wurde (Seite 2).
    Einfach mal duchlesen: Das verwirrende char in C/C++


Anmelden zum Antworten