582 Stimmen

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

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

char s[] = "hello";

oder so:

char *s = "hello";

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

613voto

Rickard Punkte 7119

Der Unterschied besteht darin, dass

char *s = "Hello world";

wird platziert "Hello world" en el Nur-Lese-Teile des Speichers und die Herstellung von s einen Zeiger darauf, der jeden Schreibvorgang auf diesen Speicher illegal macht.

Während der Arbeit:

char s[] = "Hello world";

legt die literale Zeichenkette in den Nur-Lese-Speicher und kopiert die Zeichenkette in den neu zugewiesenen Speicher auf dem Stack. Dadurch wird

s[0] = 'J';

legal.

181voto

bdonlan Punkte 213545

Zunächst einmal sind sie bei Funktionsargumenten genau gleichwertig:

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

In anderen Zusammenhängen, char * weist einen Zeiger zu, während char [] weist ein Array zu. Sie fragen sich, wohin die Zeichenkette im ersten Fall geht? Der Compiler weist heimlich ein statisches anonymes Array zu, um das String-Literal aufzunehmen. So:

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 Auswirkungen sind undefiniert (was oft einen Absturz bedeutet):

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

Bei Verwendung der Array-Syntax wird der Speicher direkt neu zugewiesen. Daher ist eine Änderung sicher:

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

Das Array lebt jedoch nur so lange, wie sein Inhalt reicht. Wenn Sie dies also in einer Funktion tun, geben Sie keinen Zeiger auf dieses Array zurück oder lassen Sie ihn auslaufen - erstellen Sie stattdessen eine Kopie mit strdup() oder ähnlich. Wenn das Array im globalen Bereich zugewiesen ist, ist das natürlich kein Problem.

89voto

caf Punkte 224189

Diese Erklärung:

char s[] = "hello";

Erzeugt un Objekt - ein char Array der Größe 6, genannt s , initialisiert mit den Werten 'h', 'e', 'l', 'l', 'o', '\0' . Wo dieses Array im Speicher zugewiesen wird und wie lange es lebt, hängt davon ab, wo die Deklaration erscheint. Befindet sich die Deklaration innerhalb einer Funktion, lebt sie bis zum Ende des Blocks, in dem sie deklariert ist, und wird mit ziemlicher Sicherheit auf dem Stack alloziert; befindet sie sich außerhalb einer Funktion, wird sie wahrscheinlich in einem "initialisierten Datensegment" gespeichert werden, 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";

Erzeugt zwei Objekte:

  • a schreibgeschützt Reihe von 6 char s, die die Werte 'h', 'e', 'l', 'l', 'o', '\0' das keinen Namen hat und statische Speicherdauer (was bedeutet, dass sie für die gesamte Laufzeit des Programms gilt); und
  • eine Variable vom Typ Zeiger-auf-Zeichen, genannt s die mit der Position des ersten Zeichens in diesem unbenannten, schreibgeschützten Array initialisiert wird.

Das unbenannte Nur-Lese-Array befindet sich in der Regel im "Text"-Segment des Programms, d. h. es wird zusammen mit dem eigentlichen Code von der Festplatte in den Nur-Lese-Speicher geladen. Der Speicherort des s Zeigervariable im Speicher hängt davon ab, wo die Deklaration erscheint (genau wie im ersten Beispiel).

72voto

John Bode Punkte 112486

Angesichts der Erklärungen

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

nehmen Sie die folgende hypothetische Speicherzuordnung an (die Spalten stehen für Zeichen an den Offsets 0 bis 3 von der gegebenen Zeilenadresse, also z. B. die 0x00 in der rechten unteren Ecke ist unter der Adresse 0x0001000C + 3 = 0x0001000F ):

                     +0    +1    +2    +3
        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 eine 12-Elemente-Anordnung von char ( const char in C++) mit statischer Speicherdauer, was bedeutet, dass der Speicher dafür beim Programmstart zugewiesen wird und bis zur Beendigung des Programms zugewiesen bleibt. Der Versuch, den Inhalt eines String-Literal zu ändern, führt zu undefiniertem Verhalten.

Die Linie

char *s0 = "hello world";

definiert s0 als Zeiger auf char mit automatischer Speicherdauer (d.h. die Variable s0 nur für den Bereich existiert, in dem es deklariert ist) und kopiert die Adresse des String-Literales ( 0x00008000 in diesem Beispiel) zu. Beachten Sie, dass seit s0 auf ein String-Literal zeigt, sollte es nicht als Argument für eine Funktion verwendet werden, die versuchen würde, es zu verändern (z. B., strtok() , strcat() , strcpy() , usw.).

Die Linie

char s1[] = "hello world";

definiert s1 als 12-Element-Array von char (Länge wird aus dem Stringliteral übernommen) mit automatischer Speicherdauer und kopiert die Inhalt des Literales in das Array. Wie Sie aus der Speicherabbildung ersehen können, haben wir zwei Kopien der Zeichenkette "hello world" Der Unterschied besteht darin, dass Sie die Zeichenkette ändern können, die in s1 .

s0 y s1 sind in den meisten Kontexten austauschbar; hier gibt es 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 zuordnen s0 auf ein anderes Zeichenfolgenliteral oder eine andere Variable verweisen. Sie können die Variable nicht neu zuweisen s1 auf ein anderes Array verweisen.

41voto

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 Typ Zeichen kann durch ein Zeichenkettenliteral initialisiert werden, optional in geschweifte Klammern eingeschlossen. Aufeinanderfolgende Zeichen des Zeichenketten-Literal (einschließlich des abschließenden Null-Zeichen, 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'};

    Wie jede andere regelmäßige Anordnung, c kann geändert werden.

  2. Überall sonst: es entsteht ein:

    Wenn Sie also schreiben:

    char *c = "abc";

    Dies ist vergleichbar mit:

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

    Beachten Sie den impliziten Cast von char[] à char * , was immer legal ist.

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

    Dies ist unter 6.4.5 "String-Literale" dokumentiert:

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

    6 Es ist nicht spezifiziert, ob diese Arrays unterschiedlich sind, sofern ihre Elemente die entsprechenden Werte haben. 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 "einfache" Char-Array-Objekte s y t deren Elemente mit Zeichenkettenliteralen initialisiert werden.

Diese Erklärung ist identisch mit

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

Der Inhalt der Arrays ist änderbar. Auf der anderen Seite ist die Deklaration

char *p = "abc";

definiert p mit dem Typ "pointer to char" und initialisiert es so, dass es auf ein Objekt mit dem Typ "array of char" mit der Länge 4 zeigt, dessen Elemente mit einem Zeichenkettenliteral initialisiert werden. Wenn ein Versuch unternommen wird, die p um den 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

Schlussfolgerung: GCC-Läden char* es in .rodata Abschnitt, nicht in .text .

Beachten Sie jedoch, dass das Standard-Linkerskript .rodata y .text in der gleichen Segment der zwar Ausführungs-, aber keine Schreibrechte hat. Dies kann mit beobachtet werden:

readelf -l a.out

die enthält:

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

Wenn wir das Gleiche tun für char[] :

 char s[] = "abc";

erhalten wir:

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

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

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X