Was ist der Unterschied zwischen char s [] und char * s?

506

In C kann man ein String-Literal in einer Deklaration wie dieser verwenden:

char s[] = "hello";

oder so:

char *s = "hello";

Was ist der Unterschied? Ich möchte wissen, was tatsächlich in Bezug auf die Speicherdauer passiert, sowohl beim Kompilieren als auch zur Laufzeit.

Geschichtenerzähler - Unslander Monica
quelle
8
char * s = "Hallo", hier kann s zur Laufzeit auf eine andere Zeichenfolge zeigen. Ich meine, es ist kein konstanter Zeiger. Sie können zur Laufzeit einen anderen Wert zuweisen. p = "Nishant", während s [] hier s ein konstanter Zeiger ist. ..es kann keine andere Zeichenfolge neu zuweisen, aber wir können einen anderen Zeichenwert bei s [Index] zuweisen.
Nishant Kumar

Antworten:

541

Der Unterschied hier ist das

char *s = "Hello world";

wird "Hello world"in den schreibgeschützten Teilen des Speichers platziert , und swenn Sie einen Zeiger darauf setzen, ist jede Schreiboperation in diesem Speicher unzulässig.

Währenddessen:

char s[] = "Hello world";

Setzt die Literalzeichenfolge in den Nur-Lese-Speicher und kopiert die Zeichenfolge in den neu zugewiesenen Speicher auf dem Stapel. So machen

s[0] = 'J';

legal.

Rickard
quelle
22
Die Literalzeichenfolge "Hello world"befindet sich in beiden Beispielen in "schreibgeschützten Teilen des Speichers". Das Beispiel mit dem Array zeigt dort, das Beispiel mit dem Array kopiert die Zeichen in die Array-Elemente.
PMG
28
pmg: Im zweiten Fall existiert die Literalzeichenfolge nicht unbedingt als einzelnes zusammenhängendes Objekt im Speicher - es ist nur ein Initialisierer. Der Compiler könnte durchaus eine Reihe von Anweisungen zum Laden von Sofortbytes ausgeben, die die darin eingebetteten Zeichenwerte enthalten Sie.
Café
10
Das Beispiel für ein char-Array platziert die Zeichenfolge nicht unbedingt auf dem Stapel. Wenn sie auf Dateiebene angezeigt wird, befindet sie sich wahrscheinlich stattdessen in einer Art initialisiertem Datensegment.
Café
9
Ich möchte darauf hinweisen , dass char s = „xx“ nicht haben in Nur - Lese-Speicher sein (einige Implementierungen haben keine MMU, zum Beispiel). Der c1x-Entwurf von n1362 besagt lediglich, dass das Ändern eines solchen Arrays undefiniertes Verhalten verursacht. Aber +1 trotzdem, da es dumm ist, sich auf dieses Verhalten zu verlassen.
Paxdiablo
3
Ich bekomme eine saubere Kompilierung für eine Datei, die nur char msg[] = "hello, world!"; die Zeichenfolge enthält, die im initialisierten Datenabschnitt landet. Wenn deklariert char * const, dass es im schreibgeschützten Datenbereich landet. gcc-4.5.3
gcbenison
152

Zunächst einmal sind sie in Funktionsargumenten genau gleichwertig:

void foo(char *x);
void foo(char x[]); // exactly the same in all respects

In anderen Kontexten char *wird ein Zeiger char []zugewiesen , während ein Array zugewiesen wird. Wohin geht die Saite im ersteren Fall, fragen Sie? Der Compiler weist heimlich ein statisches anonymes Array zu, das das String-Literal enthält. Damit:

char *x = "Foo";
// is approximately equivalent to:
static const char __secret_anonymous_array[] = "Foo";
char *x = (char *) __secret_anonymous_array;

Beachten Sie, dass Sie niemals versuchen dürfen, den Inhalt dieses anonymen Arrays über diesen Zeiger zu ändern. Die Effekte sind undefiniert (was oft einen Absturz bedeutet):

x[1] = 'O'; // BAD. DON'T DO THIS.

Durch die Verwendung der Array-Syntax wird sie direkt einem neuen Speicher zugewiesen. Somit ist eine Änderung sicher:

char x[] = "Foo";
x[1] = 'O'; // No problem.

Das Array lebt jedoch nur so lange wie sein kontanierender Bereich. Wenn Sie dies in einer Funktion tun, geben Sie keinen Zeiger auf dieses Array zurück oder verlieren Sie es - erstellen Sie stattdessen eine Kopie mit strdup()oder ähnlichem. Wenn das Array im globalen Bereich zugeordnet ist, ist dies natürlich kein Problem.

bdonlan
quelle
72

Diese Erklärung:

char s[] = "hello";

Erstellt ein Objekt - ein charArray der Größe 6, das smit den Werten initialisiert wird 'h', 'e', 'l', 'l', 'o', '\0'. Wo dieses Array im Speicher zugeordnet ist und wie lange es lebt, hängt davon ab, wo die Deklaration angezeigt wird. Wenn sich die Deklaration innerhalb einer Funktion befindet, bleibt sie bis zum Ende des Blocks bestehen, in dem sie deklariert ist, und wird mit ziemlicher Sicherheit auf dem Stapel zugewiesen. Wenn es sich außerhalb einer Funktion befindet, wird es wahrscheinlich in einem "initialisierten Datensegment" gespeichert, das beim Ausführen des Programms aus der ausführbaren Datei in den beschreibbaren Speicher geladen wird.

Auf der anderen Seite diese Erklärung:

char *s ="hello";

Erstellt zwei Objekte:

  • ein schreibgeschütztes Array von 6 chars, das die Werte enthält 'h', 'e', 'l', 'l', 'o', '\0', keinen Namen hat und eine statische Speicherdauer hat (was bedeutet, dass es für die gesamte Lebensdauer des Programms gültig ist); und
  • Eine Variable vom Typ Zeiger auf sZeichen , aufgerufen , die mit der Position des ersten Zeichens in diesem unbenannten schreibgeschützten Array initialisiert wird.

Das unbenannte schreibgeschützte Array befindet sich normalerweise im "Text" -Segment des Programms. Dies bedeutet, dass es zusammen mit dem Code selbst von der Festplatte in den Nur-Lese-Speicher geladen wird. Die Position der sZeigervariablen im Speicher hängt davon ab, wo die Deklaration angezeigt wird (genau wie im ersten Beispiel).

caf
quelle
1
In beiden Deklarationen für "Hallo" wird der Speicher zur gleichen Zeit zugewiesen? Und noch etwas char * p = "Hallo" hier wird "Hallo" im Textsegment gespeichert, wie Sie in Ihrer Antwort angegeben haben ... und was ist mit char s [] = "Hallo" wird es auch zuerst im Textsegmentteil gespeichert und während der Laufzeit im Stapel kopiert, wie Rickard in seiner Antwort angegeben hat. Bitte klären Sie diesen Punkt.
Nishant Kumar
2
@Nishant: In diesem char s[] = "hello"Fall ist das "hello"nur ein Initialisierer, der dem Compiler mitteilt, wie das Array initialisiert werden soll. Dies kann zu einer entsprechenden Zeichenfolge im Textsegment führen oder nicht. Wenn beispielsweise seine statische Speicherdauer vorliegt, ist es wahrscheinlich, dass sich die einzige Instanz von "hello"im initialisierten Datensegment befindet - dem Objekt sselbst. Selbst wenn sdie Speicherdauer automatisch beträgt, kann sie durch eine Folge von Literalspeichern und nicht durch eine Kopie (z. B. movl $1819043176, -6(%ebp); movw $111, -2(%ebp)) initialisiert werden .
Café
Genauer gesagt, GCC 4.8 fügt es ein, in .rodatadas das Linker-Skript dann in dasselbe Segment wie .text. Siehe meine Antwort .
Ciro Santilli 法轮功 冠状 病 六四 事件 5
@caf In der ersten Antwort von Rickard wird geschrieben, dass char s[] = "Hello world";die Literalzeichenfolge im Nur-Lese-Speicher abgelegt und die Zeichenfolge in den neu zugewiesenen Speicher auf dem Stapel kopiert wird. Ihre Antwort spricht jedoch nur über die wörtliche Zeichenfolge, die im Nur-Lese-Speicher abgelegt ist, und überspringt den zweiten Teil des Satzes, der besagt : copies the string to newly allocated memory on the stack. Ist Ihre Antwort unvollständig, weil Sie den zweiten Teil nicht angegeben haben?
KPMG
1
@AjaySinghNegi: Wie ich in anderen Kommentaren (zu dieser Antwort und zu Rickards Antwort) angegeben habe, ist die Zeichenfolge char s[] = "Hellow world";nur ein Initialisierer und wird nicht unbedingt als separate schreibgeschützte Kopie gespeichert. Wenn seine statische Speicherdauer vorliegt, befindet sich die einzige Kopie der Zeichenfolge wahrscheinlich in einem Lese- / Schreibsegment an der Position von s, und selbst wenn dies nicht der Fall ist, kann der Compiler das Array mit Anweisungen zum sofortigen Laden oder ähnlichem initialisieren, anstatt zu kopieren von einer schreibgeschützten Zeichenfolge. Der Punkt ist, dass in diesem Fall die Initialisierungszeichenfolge selbst keine Laufzeitpräsenz hat.
Café
60

Angesichts der Erklärungen

char *s0 = "hello world";
char s1[] = "hello world";

Nehmen Sie die folgende hypothetische Speicherkarte an:

                    0x01 0x02 0x03 0x04
        0x00008000: 'h' 'e' 'l' 'l'
        0x00008004: 'o' '' 'w' 'o'
        0x00008008: 'r' 'l' 'd' 0x00
        ...
s0: 0x00010000: 0x00 0x00 0x80 0x00
s1: 0x00010004: 'h' 'e' 'l' 'l'
        0x00010008: 'o' '' 'w' 'o'
        0x0001000C: 'r' 'l' 'd' 0x00

Das String-Literal "hello world"ist ein 12-Elemente-Array von char( const charin C ++) mit statischer Speicherdauer. Dies bedeutet, dass der Speicher dafür beim Start des Programms zugewiesen wird und bis zum Beenden des Programms zugewiesen bleibt. Der Versuch, den Inhalt eines Zeichenfolgenliteral zu ändern, führt zu undefiniertem Verhalten.

Die Linie

char *s0 = "hello world";

definiert s0als Zeiger auf charmit automatischer Speicherdauer (dh die Variable s0existiert nur für den Bereich, in dem sie deklariert ist) und kopiert die Adresse des Zeichenfolgenliteral ( 0x00008000in diesem Beispiel) in diese. Beachten Sie, dass da s0Punkte zu einem Stringliteral, ist es nicht als Argument für jede Funktion verwendet werden soll , die es zu ändern versuchen würden ( zum Beispiel strtok(), strcat(), strcpy(), etc.).

Die Linie

char s1[] = "hello world";

definiert s1als ein 12-Elemente-Array von char(Länge wird aus dem Zeichenfolgenliteral entnommen) mit automatischer Speicherdauer und kopiert den Inhalt des Literal in das Array. Wie Sie der Speicherzuordnung entnehmen können, haben wir zwei Kopien der Zeichenfolge "hello world". Der Unterschied besteht darin, dass Sie die in enthaltene Zeichenfolge ändern können s1.

s0und s1sind in den meisten Kontexten austauschbar; Hier sind die Ausnahmen:

sizeof s0 == sizeof (char*)
sizeof s1 == 12

type of &s0 == char **
type of &s1 == char (*)[12] // pointer to a 12-element array of char

Sie können die Variable neu zuweisen s0, um auf ein anderes Zeichenfolgenliteral oder eine andere Variable zu verweisen. Sie können die Variable nicht neu zuweisen s1, um auf ein anderes Array zu verweisen.

John Bode
quelle
2
Ich denke, die hypothetische Speicherkarte macht es leicht zu verstehen!
MitternachtBlau
32

C99 N1256 Entwurf

Es gibt zwei verschiedene Verwendungen von Zeichenkettenliteralen:

  1. Initialisieren char[]:

    char c[] = "abc";      

    Dies ist "mehr Magie" und wird unter 6.7.8 / 14 "Initialisierung" beschrieben:

    Ein Array vom Zeichentyp kann durch ein Zeichenfolgenliteral initialisiert werden, das optional in geschweiften Klammern eingeschlossen ist. Aufeinanderfolgende Zeichen des Zeichenfolgenliteral (einschließlich des abschließenden Nullzeichens, wenn Platz vorhanden ist oder wenn das Array eine unbekannte Größe hat) initialisieren die Elemente des Arrays.

    Dies ist also nur eine Abkürzung für:

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

    cKann wie jedes andere reguläre Array geändert werden.

  2. Überall sonst: es erzeugt ein:

    Also, wenn Sie schreiben:

    char *c = "abc";

    Dies ist ähnlich wie:

    /* __unnamed is magic because modifying it gives UB. */
    static char __unnamed[] = "abc";
    char *c = __unnamed;

    Beachten Sie die implizite Besetzung von char[]bis char *, die immer legal ist.

    Wenn Sie dann ändern c[0], ändern Sie auch __unnamed, was UB ist.

    Dies ist unter 6.4.5 "String-Literale" dokumentiert:

    5 In der Übersetzungsphase 7 wird an jede Multibyte-Zeichenfolge, die sich aus einem String-Literal oder Literalen ergibt, ein Byte oder ein Code mit dem Wert Null angehängt. Die Multibyte-Zeichenfolge wird dann verwendet, um ein Array mit statischer Speicherdauer und -länge zu initialisieren, das gerade ausreicht, um die Folge aufzunehmen. Bei Zeichenfolgenliteralen haben die Array-Elemente den Typ char und werden mit den einzelnen Bytes der Multibyte-Zeichenfolge [...] initialisiert.

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

6.7.8 / 32 "Initialisierung" gibt ein direktes Beispiel:

BEISPIEL 8: Die Erklärung

char s[] = "abc", t[3] = "abc";

definiert „plain“ char Array - Objekte sund tderen Elemente mit Zeichenfolge Literale initialisiert.

Diese Erklärung ist identisch mit

char s[] = { 'a', 'b', 'c', '\0' },
t[] = { 'a', 'b', 'c' };

Der Inhalt der Arrays kann geändert werden. Auf der anderen Seite die Erklärung

char *p = "abc";

definiert pmit dem Typ "Zeiger auf Zeichen" und initialisiert es so, dass es auf ein Objekt mit dem Typ "Array von Zeichen" mit der Länge 4 zeigt, dessen Elemente mit einem Zeichenfolgenliteral initialisiert werden. Wenn versucht wird, pden Inhalt des Arrays zu ändern, ist das Verhalten undefiniert.

GCC 4.8 x86-64 ELF-Implementierung

Programm:

#include <stdio.h>

int main(void) {
    char *s = "abc";
    printf("%s\n", s);
    return 0;
}

Kompilieren und dekompilieren:

gcc -ggdb -std=c99 -c main.c
objdump -Sr main.o

Die Ausgabe enthält:

 char *s = "abc";
8:  48 c7 45 f8 00 00 00    movq   $0x0,-0x8(%rbp)
f:  00 
        c: R_X86_64_32S .rodata

Fazit: GCC speichert char*es in .rodataAbschnitt, nicht in .text.

Beachten Sie jedoch , dass der Standard Linker - Skript setzt .rodataund .textim gleichen Segment , das auszuführen hat , aber keine Schreibrechte. Dies kann beobachtet werden mit:

readelf -l a.out

was beinhaltet:

 Section to Segment mapping:
  Segment Sections...
   02     .text .rodata

Wenn wir dasselbe tun für char[]:

 char s[] = "abc";

wir erhalten:

17:   c7 45 f0 61 62 63 00    movl   $0x636261,-0x10(%rbp)

so wird es im Stapel gespeichert (relativ zu %rbp).

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
quelle
15
char s[] = "hello";

deklariert s, dass es sich um ein Array handelt, chardas lang genug ist, um den Initialisierer (5 + 1 chars) aufzunehmen, und initialisiert das Array durch Kopieren der Elemente des angegebenen Zeichenfolgenliteral in das Array.

char *s = "hello";

deklariert s, ein Zeiger auf ein oder mehrere (in diesem Fall mehrere) chars zu sein und zeigt direkt auf eine feste (schreibgeschützte) Stelle, die das Literal enthält "hello".

CB Bailey
quelle
1
Welche Methode ist in Funktionen vorzuziehen, wenn s nicht geändert wird, f (const char s []) oder f (const char * s)?
Psihodelia
1
@psihodelia: In einer Funktionsdeklaration gibt es keinen Unterschied. In beiden Fällen sist ein Zeiger auf const char.
CB Bailey
4
char s[] = "Hello world";

Hier sist eine Reihe von Zeichen, die auf Wunsch überschrieben werden können.

char *s = "hello";

Ein Zeichenfolgenliteral wird verwendet, um diese Zeichenblöcke irgendwo im Speicher zu erstellen, auf den dieser Zeiger szeigt. Wir können hier das Objekt, auf das es zeigt, neu zuweisen, indem wir es ändern, aber solange es auf ein Zeichenfolgenliteral zeigt, kann der Zeichenblock, auf den es zeigt, nicht geändert werden.

Sailaja
quelle
@bo Persson Warum kann der Zeichenblock im zweiten Fall nicht geändert werden?
Pankaj Mahato
3

Beachten Sie außerdem, dass Sie für schreibgeschützte Zwecke die Verwendung von beiden identisch sind. Sie können auf ein Zeichen zugreifen, indem Sie entweder mit []oder im *(<var> + <index>) Format indizieren :

printf("%c", x[1]);     //Prints r

Und:

printf("%c", *(x + 1)); //Prints r

Offensichtlich, wenn Sie versuchen, dies zu tun

*(x + 1) = 'a';

Sie werden wahrscheinlich einen Segmentierungsfehler erhalten, wenn Sie versuchen, auf den Nur-Lese-Speicher zuzugreifen.

Nick Louloudakis
quelle
Dies unterscheidet sich in keiner Weise von x[1] = 'a';dem, bei dem auch ein Fehler auftritt (natürlich abhängig von der Plattform).
glglgl
3

Nur um hinzuzufügen: Sie erhalten auch unterschiedliche Werte für ihre Größen.

printf("sizeof s[] = %zu\n", sizeof(s));  //6
printf("sizeof *s  = %zu\n", sizeof(s));  //4 or 8

Wie oben erwähnt, wird für ein Array '\0'das letzte Element zugewiesen.

Muzab
quelle
2
char *str = "Hello";

Die obigen Sätze str zeigen auf den Literalwert "Hello", der im Binärbild des Programms fest codiert ist, das im Speicher als schreibgeschützt gekennzeichnet ist. Dies bedeutet, dass jede Änderung in diesem String-Literal unzulässig ist und Segmentierungsfehler verursachen würde.

char str[] = "Hello";

kopiert die Zeichenfolge in den neu zugewiesenen Speicher auf dem Stapel. Änderungen daran sind daher zulässig und legal.

means str[0] = 'M';

ändert die str in "Mello".

Für weitere Details gehen Sie bitte die ähnliche Frage durch:

Warum erhalte ich einen Segmentierungsfehler, wenn ich in eine Zeichenfolge schreibe, die mit "char * s", aber nicht mit "char s []" initialisiert wurde?

Mohit
quelle
0

Im Falle des:

char *x = "fred";

x ist ein l-Wert - er kann zugewiesen werden. Aber im Fall von:

char x[] = "fred";

x ist kein l-Wert, es ist ein r-Wert - Sie können ihm keinen Wert zuweisen.

Lee-Man
quelle
3
Technisch xist ein nicht modifizierbarer Wert. In fast allen Kontexten wird jedoch ein Zeiger auf sein erstes Element ausgewertet, und dieser Wert ist ein r-Wert.
Café
0
char *s1 = "Hello world"; // Points to fixed character string which is not allowed to modify
char s2[] = "Hello world"; // As good as fixed array of characters in string so allowed to modify

// s1[0] = 'J'; // Illegal
s2[0] = 'J'; // Legal
Atul
quelle
-1

In Anbetracht der Kommentare hier sollte es offensichtlich sein, dass: char * s = "hallo"; Ist eine schlechte Idee und sollte in sehr engem Umfang verwendet werden.

Dies könnte eine gute Gelegenheit sein, darauf hinzuweisen, dass "konstante Korrektheit" eine "gute Sache" ist. Wann und wo immer Sie können, verwenden Sie das Schlüsselwort "const", um Ihren Code vor "entspannten" Anrufern oder Programmierern zu schützen, die normalerweise am "entspanntesten" sind, wenn Zeiger ins Spiel kommen.

Genug Melodram, hier ist, was man erreichen kann, wenn man Zeiger mit "const" schmückt. (Hinweis: Sie müssen Zeigerdeklarationen von rechts nach links lesen.) Hier sind die drei verschiedenen Möglichkeiten, sich beim Spielen mit Zeigern zu schützen:

const DBJ* p means "p points to a DBJ that is const" 

- das heißt, das DBJ-Objekt kann nicht über p geändert werden.

DBJ* const p means "p is a const pointer to a DBJ" 

- Das heißt, Sie können das DBJ-Objekt über p ändern, aber Sie können den Zeiger p selbst nicht ändern.

const DBJ* const p means "p is a const pointer to a const DBJ" 

- Das heißt, Sie können weder den Zeiger p selbst noch das DBJ-Objekt über p ändern.

Die Fehler im Zusammenhang mit versuchten Konstantenmutationen werden zur Kompilierungszeit abgefangen. Es gibt keinen Laufzeitraum oder Geschwindigkeitsverlust für const.

(Angenommen, Sie verwenden natürlich den C ++ - Compiler?)

- DBJ


quelle
Das ist alles richtig, hat aber nichts mit der Frage zu tun. Und was Ihre Annahme über einen C ++ - Compiler betrifft, ist die Frage als C und nicht als C ++ gekennzeichnet.
Fabio sagt Reinstate Monica
Char * s = "const string" ist nichts Schlechtes.
Paul Smith