Wenn Zeichen schreibgeschützt sind, warum kann ich sie überschreiben?

Lesezeit: 12 Minuten

Benutzer-Avatar
sally2000

Mein Kurs hat mir beigebracht, dass char * s statisch / schreibgeschützt sind, also dachte ich, das würde bedeuten, dass Sie sie nicht bearbeiten können, nachdem Sie sie definiert haben. Aber wenn ich laufe:

char* fruit = "banana";
printf("fruit is %s\n", fruit);
fruit = "apple";
printf("fruit is %s\n", fruit);

Dann kompiliert es gut und gibt mir:

fruit is banana
fruit is apple

Wieso den? Habe ich falsch verstanden, was es bedeutet, schreibgeschützt zu sein? Tut mir leid, wenn dies offensichtlich ist, aber ich bin neu in der Programmierung und kann die Antwort online nicht finden.

  • Du hast nichts überschrieben. Du hast nur gezeigt fruit an einer anderen Saite.

    – r3mainer

    31. Mai 2017 um 21:04 Uhr

  • Versuchen const char * const fruit = "banana";

    – BLUEPIXY

    31. Mai 2017 um 21:06 Uhr

  • EIN char* ist nicht schreibgeschützt. Ein String-Literal wie "banana" oder "apple" ist schreibgeschützt. C erlaubt char* auf ein String-Literal zeigen.

    – Kevin

    31. Mai 2017 um 21:09 Uhr


  • Es findet keine Umverteilung statt. Beide "banana" und "apple" sind beim Programmstart bereits im statischen Speicher allokiert. Alles, was Sie tun, ist, den Wert von zu ändern fruit die Adresse von wo sein "apple" wird gelagert.

    – Kevin

    31. Mai 2017 um 21:14 Uhr

  • @sally2000, stellen Sie sich das so vor, wenn Sie einen Zeiger deklarieren und ihn so initialisieren, dass er darauf zeigt "banna"initialisieren Sie den Zeiger so, dass er auf die Adresse des ersten Buchstabens zeigt 'b'. (zB im char* fruit = "banana";). fruit (ein Zeiger) hält nur die Adresse von 'b' als seine Wert. Wenn Sie zuweisen fruit = "apple";ändern Sie einfach den Wert, der von gehalten wird fruit zum Adresse von 'a'. Also jetzt fruit verweist auf das 'a' in "apple" (was den Zugriff auf "apple" Als ein Schnur)

    – David C. Rankin

    31. Mai 2017 um 21:20 Uhr


Benutzer-Avatar
Vlad aus Moskau

Das dargestellte Code-Snippet ändert die String-Literale selbst nicht. Es ändert nur die im Zeiger gespeicherten Werte fruit.

Sie können sich diese Linien vorstellen

char* fruit = "banana";
fruit = "apple";

folgenden Weg

char unnamed_static_array_banana[] = { 'b', 'a', 'n', 'a', 'n', 'a', '\0' };
char *fruit = &unnamed_static_array_banana[0];
char unnamed_static_array_apple[]  = { 'a', 'p', 'p', 'l', 'e', '\0' };
fruit = &unnamed_static_array_apple[0];

Diese Anweisungen ändern nicht die Arrays, die den Zeichenfolgenliteralen entsprechen.

Auf der anderen Seite, wenn Sie versuchen zu schreiben

char* fruit = "banana";
printf("fruit is %s\n", fruit);
fruit[0] = 'h';
^^^^^^^^^^^^^^
printf("fruit is %s\n", fruit);

Das heißt, wenn Sie versuchen, ein Zeichenfolgenliteral mithilfe eines Zeigers zu ändern, der darauf zeigt (auf das erste Zeichen des Zeichenfolgenliterals), zeigt das Programm ein undefiniertes Verhalten.

Aus dem C-Standard (6.4.5 String-Literale)

7 Es ist nicht spezifiziert, ob diese Arrays unterschiedlich sind, vorausgesetzt, ihre Elemente haben die entsprechenden Werte. Wenn das Programm versucht, ein solches Array zu ändern, ist das Verhalten undefiniert.

  • mm ok, aber wenn ich char verwende[] stattdessen zB Obst [10] = {‘a’,’p’,’p’,’l’,’e’,’\0′} ist Fruit hier nicht auch nur ein Zeiger auf die Speicheradresse von ‘a’? wie kommt es, dass ich char ändern kann[] aber nicht char*?

    – sally2000

    31. Mai 2017 um 21:21 Uhr


  • @sally2000 Wenn Sie ein Zeichenarray anstelle eines Zeichenfolgenliterals verwenden und einen Zeiger auf das erste Zeichen des Zeichenarrays haben, können Sie ihn ändern, vorausgesetzt, dass das Array nicht mit dem Qualifizierer const deklariert ist.

    – Vlad aus Moskau

    31. Mai 2017 um 21:22 Uhr


  • @sally2000 Intern werden String-Literale als Arrays mit der statischen Speicherdauer gespeichert, die nicht geändert werden darf.

    – Vlad aus Moskau

    31. Mai 2017 um 21:24 Uhr

  • Das String-Literal könnte sich in einigen Implementierungen im Nur-Lese-Speicher befinden (Harvard-artige Maschinen oder etwas, das direkt aus dem ROM läuft, oder eine mmap-ed Shared Library auf einem modernen Protected-Mode-System …) … das Schreiben würde es tun einfach fehlschlagen oder sogar eine Hardware-Ausnahme auslösen … in anderen Umgebungen würden Sie es versuchen und erfolgreich sein …

    – Rackandboneman

    31. Mai 2017 um 23:07 Uhr

  • @sally2000: NEIN Wenn Sie ein Array haben, fruit ist kein Zeiger – es ist ein Array. Nun ist es wahr, dass, wenn Sie den Namen eines Arrays verwenden, dieser in einen Zeiger auf char zerfällt, aber es ist eine Anordnung. Zum Beispiel, wenn Sie schreiben char fruit [10] = {'a','p','p','l','e','\0'}; dann kannst du nicht schreiben fruit = "banana";. Auch das wirst du finden sizeof(fruit); ist 10, nicht sizeof(char*) (was wahrscheinlich 4 oder 8 ist).

    – Martin Bonner unterstützt Monika

    1. Juni 2017 um 7:28 Uhr


Benutzer-Avatar
Kaz

In Ihrem Programm ist der Ausdruck "banana" bezeichnet ein Zeichenfolgenliteralobjekt im Programmbild, ein Zeichenarray. Der Wert des Ausdrucks ist vom Typ char *, oder “Zeiger auf Zeichen”. Der Zeiger zeigt auf das erste Byte dieses Arrays, das Zeichen 'b'.

Dein char *fruit Variable hat auch den Typ “Zeiger auf Zeichen” und erhält ihren Anfangswert von diesem Ausdruck: Sie wird mit einer Kopie des Zeigers auf die Daten initialisiert, nicht mit den Daten selbst; es weist lediglich auf die hin b.

Wenn Sie zuweisen "apple" zu fruitersetzen Sie nur seinen Zeigerwert durch einen anderen, sodass er jetzt auf ein anderes Literalarray zeigt.

Um die Daten selbst zu ändern, benötigen Sie einen Ausdruck wie:

char *fruit = "banana";
fruit[0] = 'z';  /* try to turn "banana" into "zanana" */

Gemäß der ISO-C-Norm ist das Verhalten hierfür nicht definiert. Es könnte sei das "banana" Das Array ist schreibgeschützt, aber das ist nicht erforderlich.

C-Implementierungen können String-Literale schreibbar machen oder es zu einer Option machen.

(Wenn Sie in der Lage sind, ein String-Literal zu ändern, heißt das nicht, dass alles in Ordnung ist. Erstens ist Ihr Programm nach ISO C immer noch nicht gut definiert: Es ist nicht portierbar. Zweitens darf der C-Compiler Literale zusammenführen die denselben Inhalt haben, in denselben Speicher. Das bedeutet, dass zwei Vorkommen von "banana" im Programm könnte tatsächlich genau das gleiche Array sein. Außerdem das String-Literal "nana" irgendwo im Programm vorkommen könnte das Suffix des Arrays sein "banana" woanders auftreten; mit anderen Worten, teilen Sie sich denselben Speicher. Das Ändern eines Literals kann überraschende Auswirkungen haben; die Modifikation kann in anderen Literalen erscheinen.)

Auch “statisch” und “schreibgeschützt” sind nicht synonym. Die meisten statischen Speicher in C sind tatsächlich modifizierbar. Wir können ein modifizierbares statisches Zeichenarray erstellen, das eine Zeichenfolge wie diese enthält:

/* at file scope, i.e. outside of any function */
char fruit[] = "banana";

Oder:

{
  /* in a function */
  static fruit[] = "banana";

Wenn wir die Array-Größe weglassen, wird die Größe automatisch vom initialisierenden String-Literal bestimmt und enthält Platz für das Null-Endbyte. In der Funktion brauchen wir static um das Array in den statischen Speicher zu legen, sonst erhalten wir eine lokale Variable.

Diese Arrays können geändert werden; fruit[0] = 'z' ist wohldefiniertes Verhalten.

Auch in diesen Situationen "banana" bezeichnet kein Zeichenarray. Das Array ist die Variable fruit; das "banana" Ausdruck ist nur ein Stück Syntax, das den Anfangswert des Arrays angibt:

char *fruit = "banana";  // "banana" is an object in program image
                         // initial value is a pointer to that object

char fruit_array[] = "apple"; // "apple" is syntax giving initial value

Das fruit Das Objekt ist beschreibbar – es kann so eingestellt werden, dass es auf ein anderes Zeichenfolgenliteral zeigt.

Das Zeichenfolgenliterale "banana" und "apple" sind nicht beschreibbar. Sie können ändern fruit auf ein Zeichenfolgenliteral zeigen, aber wenn Sie dies tun, sollten Sie nicht versuchen, das Ding that zu ändern fruit verweist auf:

char *fruit = "banana"; // fruit points to first character of string literal
fruit = "apple";        // okay, fruit points to first character of different string literal
*fruit="A";           // not okay, attempting to modify contents of string literal
fruit[1] = 'P';         // not okay, attempting to modify contents of string literal

Der Versuch, den Inhalt eines Zeichenfolgenliterals zu ändern, führt zu undefiniertem Verhalten – Ihr Code funktioniert möglicherweise wie erwartet, oder Sie erhalten einen Laufzeitfehler, oder es kann etwas völlig Unerwartetes passieren. Wenn Sie eine Variable so definieren, dass sie auf ein String-Literal zeigt, sollten Sie sie sicherheitshalber deklarieren const:

const char *fruit = "banana";  // can also be written char const *

Sie können immer noch zuweisen fruit auf verschiedene Zeichenfolgen zeigen:

fruit = "apple";

aber wenn Sie versuchen, was zu ändern fruit zeigt, wird der Compiler Sie anschreien.

Wenn Sie einen Zeiger definieren möchten, der nur auf ein bestimmtes Zeichenfolgenliteral zeigen kann, können Sie dies tun const-Qualifizieren Sie auch den Zeiger:

const char * const fruit = "banana"; // can also be written char const * const

Auf diese Weise, wenn Sie versuchen, entweder was zu schreiben fruit weist darauf hin, oder versuchen Sie es einzustellen fruit auf ein anderes Objekt zu zeigen, wird der Compiler Sie anschreien.

Grundsätzlich, wenn Sie auftreten

char* fruit = "banana";

Sie richten einen Zeiger ein fruit bis zum Anfangsbuchstaben von “Banane”. Beim Ausdrucken beginnt C grundsätzlich beim ‘b’ und druckt so lange Buchstaben, bis es auf a trifft \0 Nullzeichen am Ende.

Bis dahin sagen

fruit = "apple";

Sie haben den Zeiger geändert fruit jetzt auf den Anfangsbuchstaben von “Apfel” zeigen

Zuerst, char* sind nicht schreibgeschützt. char * consts sind. Und sie unterscheiden sich von char const *. Und wörtliche Zeichenfolgen (z. B. “Banane”) sollten es sein, müssen es aber nicht.

char * const  cpfruit = "banana";
cpfruit = "apple";        // error

char const * cpfruit = "banana";
cpfruit[0] = 'x';        // error

char * ncfruit = "banana";
ncfruit[0] = 'x';        // compile will allow, but may cause run-time error.

beim Definieren eines C-Strings, aka char Array, durch doppelte Anführungszeichen "..."im folgenden Format:

char * <varName> = "<someString>"

nur die Elemente des Arrays sind unveränderlich (ihr Inhalt kann nicht geändert werden). Mit anderen Worten, die <varName> hat ein const char * Typ (ein veränderlicher Zeiger auf einen Nur-Lese-Speicher). Und jedes Mal, wenn Sie den Zuweisungsoperator mit doppelten Anführungszeichen aufrufen <varName> = "<otherString>" es ändert den Zeigerwert automatisch. Das folgende Beispiel soll einen vollständigen Überblick über die verschiedenen Möglichkeiten geben:

#include <stdio.h>

int main()
{
    char * var_1 = "Lorem";
    printf("1. %s , %p\n", var_1, var_1); // --> 1. Lorem , 0x400640

    var_1 = "ipsu";
    printf("2. %s , %p\n", var_1, var_1); // --> 2. ipsu , 0x400652

    // var_1[0] = 'x'; // --> Segmentation fault

    var_1++;
    printf("3. %s , %p\n", var_1, var_1); // --> 3. psu , 0x400653

    char var_2[] = {'L', 'o', 'r', 'e', 'm', '\0'};
    printf("4. %s , %p\n", var_2, var_2); // --> 4. Lorem , 0x7ffed0fc5381

    var_2[0] = 'x';
    printf("5. %s , %p\n", var_2, var_2); // --> 5. xorem , 0x7ffed0fc5381

    // var_2++; //error: lvalue required as increment operand

    char var_3[] = "Lorem";
    printf("6. %s , %p\n", var_3, var_3); // --> 6. Lorem , 0x7ffe36a42d5c

    // var_3 = "ipsu"; // --> error: assignment to expression with array type

    var_3[0] = 'x';
    printf("7. %s , %p\n", var_3, var_3); // --> 7. xorem , 0x7ffe36a42d5c

    char * const var_4 = "Lorem";

    // var_4 = "ipsu"; // --> error: assignment of read-only variable

    // var_4[0] = 'x'; // --> Segmentation fault

    char const * var_5 = "Lorem";
    printf("8. %s , %p\n", var_5, var_5); // --> Lorem , 0x400720

    var_5 = "ipsu";
    printf("9. %s , %p\n", var_5, var_5); // --> ipsu , 0x400732

    // var_5[0] = 'x'; // --> error: assignment of read-only location

    const char * var_6 = "Lorem";
    printf("10. %s , %p\n", var_6, var_6); // --> 10. Lorem , 0x400760

    var_6 = "ipsu";
    printf("11. %s , %p\n", var_6, var_6); // --> 11. ipsu , 0x400772

    // var_6[0] = 'x'; // --> error: assignment of read-only location

    const char const * var_7 = "Lorem"; // clang only --> warning: duplicate 'const' declaration specifier [-Wduplicate-decl-specifier]
    printf("12. %s , %p\n", var_7, var_7); // --> 12. Lorem , 0x400760

    var_7 = "ipsu";
    printf("13. %s , %p\n", var_7, var_7); // --> 13. ipsu , 0x400772

    // var_7[0] = 'x'; // --> error: assignment of read-only location

    char const const * var_8 = "Lorem"; // clang only --> warning: duplicate 'const' declaration specifier [-Wduplicate-decl-specifier]
    printf("14. %s , %p\n", var_8, var_8); // --> 14. Lorem , 0x400790

    var_8 = "ipsu";
    printf("15. %s , %p\n", var_8, var_8); // --> 15. ipsu , 0x4007a2

    // var_8[0] = 'x'; // --> error: assignment of read-only location

    char const * const var_9 = "Lorem";

    // var_9 = "ipsu"; // --> error: assignment of read-only variable

    // var_9[0] = 'x'; // --> error: assignment of read-only location

    const char var_10[] = {'L', 'o', 'r', 'e', 'm', '\0'};

    // var_10[0] = 'x'; // --> error: assignment of read-only location

    // var_10++; // --> error: lvalue required as increment operand

    char const var_11[] = {'L', 'o', 'r', 'e', 'm', '\0'};

    // var_11[0] = 'x'; // --> error: assignment of read-only location 

    // var_11++; // --> error: lvalue required as increment operand

    const char var_12[] = "Lorem";

    // var_12[0] = 'x'; // --> error: assignment of read-only location

    // var_12++; // --> error: lvalue required as increment operand

    char const var_13[] = "Lorem";

    // var_13[0] = 'x'; // --> error: assignment of read-only location

    // var_13++; // --> error: lvalue required as increment operand


    return 0;
}

Dieser Code wurde auf GCC, Clang und Visual Studio getestet.

Grundsätzlich gibt es drei Möglichkeiten:

  • unveränderlicher Zeiger, veränderlicher Inhalt

    • char <varName>[] = {'L', 'o', 'r', 'e', 'm', '\0'};
    • char <varName>[] = "Lorem";
  • veränderlicher Zeiger, unveränderlicher Inhalt

    • char * <varName> = "Lorem";
    • char const * <varName> = "Lorem";
    • const char * <varName> = "Lorem";
    • const char const * <varName> = "Lorem";
    • char const const * <varName> = "Lorem";
  • unveränderlicher Zeiger, unveränderlicher Inhalt

    • char * const <varName> = "Lorem";
    • char const * const <varName> = "Lorem";
    • const char * const <varName> = "Lorem";
    • const char <varName>[] = {'L', 'o', 'r', 'e', 'm', '\0'};
    • char const <varName>[] = {'L', 'o', 'r', 'e', 'm', '\0'};
    • const char <varName>[] = "Lorem";
    • char const <varName>[] = "Lorem";

Fazit:

  • <typing> <varName>[] = <string> gibt immer einen unveränderlichen Zeiger zurück und die Veränderlichkeit des Inhalts ist unabhängig von der <array> Format ("Lorem" oder {'L', 'o', 'r', 'e', 'm', '\0'})
  • <typing> * <varName> = "someString" gibt immer unveränderlichen Inhalt zurück
  • <typing> * const <varName> = "someString" gibt immer sowohl unveränderlichen Inhalt als auch Zeiger zurück
  • char const <other>, char const <other>, const char const <other>und char const const <other> Erstellen Sie immer unveränderliche Inhalte.

Ich habe versucht, das Array-Verhalten von C zusammenzufassen hier im Detail.

Benutzer-Avatar
arminb

Sie zeigen Ihre Variable fruit zu einer anderen Saite. Sie überschreiben nur die Adresse (Ort). Der Compiler sieht Ihre konstanten Zeichenfolgen “Banane” und “Apfel” und speichert sie separat im Programmspeicher. Angenommen, die Zeichenfolge “Banane” geht an die Speicherzelle, die sich an der Adresse befindet 1 und “Apfel” wird in der Speicheradresse gespeichert 2. Wenn du es jetzt tust:

fruit = "banana";

der Compiler wird nur zuweisen 1 zu variabel fruitwas bedeutet, dass es auf eine Adresse zeigt 1 die den String enthält banana. Wenn Sie das tun:

fruit = "apple";

der Compiler wird zuweisen 2 Variable fruitwas bedeutet, dass es auf Adresse zeigt 2 wo die Schnur apple wird gelagert.

  • Eine Kleinigkeit, aber der Compiler wird das sehen Zeichenfolgenliterale "banana" und "apple" und bieten Speicher für sie in der .rodata (schreibgeschützte Daten) Abschnitt. (ich glaube du sagst dasselbe)

    – David C. Rankin

    31. Mai 2017 um 21:29 Uhr

  • Ja, wollte es hier einfach halten.

    – arminb

    31. Mai 2017 um 21:30 Uhr

1310740cookie-checkWenn Zeichen schreibgeschützt sind, warum kann ich sie überschreiben?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy