Was ist der Unterschied zwischen char-Array und char-Zeiger in C?

216

Ich versuche, Zeiger in C zu verstehen, bin aber derzeit mit Folgendem verwechselt:

  • char *p = "hello"

    Dies ist ein Zeichenzeiger, der auf das Zeichenarray zeigt, beginnend bei h .

  • char p[] = "hello"

    Dies ist ein Array, das Hallo speichert .

Was ist der Unterschied, wenn ich beide Variablen an diese Funktion übergebe?

void printSomething(char *p)
{
    printf("p: %s",p);
}
Diesel
quelle
5
Dies wäre nicht gültig: char p[3] = "hello";Die Initialisierungszeichenfolge ist zu lang für die Größe des von Ihnen deklarierten Arrays. Tippfehler?
Cody Gray
16
Oder char p[]="hello";würde einfach ausreichen!
Deepdive
mögliches Duplikat von C: Unterschiede zwischen
Zeichenzeiger
1
mögliches Duplikat von Was ist der Unterschied zwischen char s [] und char * s in C? Dies fragt zwar auch speziell nach dem Funktionsparameter, aber das ist nicht charspezifisch.
Ciro Santilli 法轮功 冠状 病 六四 事件 5
1
Sie müssen verstehen, dass sie sich grundlegend unterscheiden. Die einzige Gemeinsamkeit besteht darin, dass die Basis des Arrys p [] ein konstanter Zeiger ist, der den Zugriff auf das Array p [] über einen Zeiger ermöglicht. p [] selbst enthält Speicher für eine Zeichenfolge, während * p nur auf die Adresse des ersten Elements von nur EINEM CHAR zeigt (dh auf die Basis der bereits zugewiesenen Zeichenfolge zeigt). Um dies besser zu veranschaulichen, betrachten Sie unten: char * cPtr = {'h', 'e', ​​'l', 'l', 'o', '\ 0'}; ==> Dies ist ein Fehler, da cPtr nur ein Zeiger auf ein Zeichen ist. char cBuff [] = {'h', 'e', ​​'l', 'l', 'o', '\ 0'}; ==> Dies ist in Ordnung, bcos cBuff selbst ist ein char-Array
Ilavarasan

Antworten:

222

char*und char[] sind verschiedene Typen , aber es ist nicht in allen Fällen sofort ersichtlich. Dies liegt daran, dass Arrays in Zeiger zerfallen. Wenn also ein Ausdruck vom Typ char[]bereitgestellt wird, bei dem einer vom Typ char*erwartet wird, konvertiert der Compiler das Array automatisch in einen Zeiger auf sein erstes Element.

Ihre Beispielfunktion printSomethingerwartet einen Zeiger. Wenn Sie also versuchen, ein Array wie folgt zu übergeben:

char s[10] = "hello";
printSomething(s);

Der Compiler gibt vor, Folgendes geschrieben zu haben:

char s[10] = "hello";
printSomething(&s[0]);
Jon
quelle
Hat sich von 2012 bis heute etwas geändert? Für ein Zeichenarray druckt "s" das gesamte Array. Dh "Hallo"
Bhanu Tez
@BhanuTez Nein, wie Daten gespeichert werden und was mit den Daten gemacht wird, sind separate Anliegen. In diesem Beispiel wird die gesamte Zeichenfolge gedruckt, da auf diese Weise printfdie %sFormatzeichenfolge behandelt wird: Beginnen Sie an der angegebenen Adresse und fahren Sie fort, bis Sie auf den Nullterminator stoßen. Wenn Sie nur ein Zeichen drucken möchten, können Sie beispielsweise die Formatzeichenfolge verwenden %c.
iX3
Wollten Sie nur fragen, ob char *p = "abc";das NULL-Zeichen \0automatisch angehängt wird, wie im Fall eines char [] -Arrays?
KPMG
Warum kann ich setzen, char *name; name="123";aber kann das gleiche mit intTyp tun ? Und nach %cdem Drucken nameist die Ausgabe eine unlesbare Zeichenfolge : ?
TomSawyer
83

Mal schauen:

#include <stdio.h>
#include <string.h>

int main()
{
    char *p = "hello";
    char q[] = "hello"; // no need to count this

    printf("%zu\n", sizeof(p)); // => size of pointer to char -- 4 on x86, 8 on x86-64
    printf("%zu\n", sizeof(q)); // => size of char array in memory -- 6 on both

    // size_t strlen(const char *s) and we don't get any warnings here:
    printf("%zu\n", strlen(p)); // => 5
    printf("%zu\n", strlen(q)); // => 5

    return 0;
}

foo * und foo [] sind unterschiedliche Typen und werden vom Compiler unterschiedlich behandelt (Zeiger = Adresse + Darstellung des Zeigertyps, Array = Zeiger + optionale Länge des Arrays, falls bekannt, z. B. wenn das Array statisch zugeordnet ist ) finden Sie die Details im Standard. Und auf der Ebene der Laufzeit kein Unterschied zwischen ihnen (im Assembler, na ja, fast, siehe unten).

Es gibt auch eine verwandte Frage in den C-FAQ :

F : Was ist der Unterschied zwischen diesen Initialisierungen?

char a[] = "string literal";   
char *p  = "string literal";   

Mein Programm stürzt ab, wenn ich versuche, p [i] einen neuen Wert zuzuweisen.

A : Ein String-Literal (der formale Begriff für einen String in doppelten Anführungszeichen in der C-Quelle) kann auf zwei leicht unterschiedliche Arten verwendet werden:

  1. Als Initialisierer für ein Array von char gibt er wie in der Deklaration von char a [] die Anfangswerte der Zeichen in diesem Array (und gegebenenfalls seine Größe) an.
  2. Überall sonst wird es zu einem unbenannten, statischen Array von Zeichen, und dieses unbenannte Array kann im Nur-Lese-Speicher gespeichert werden und kann daher nicht unbedingt geändert werden. In einem Ausdruckskontext wird das Array wie üblich sofort in einen Zeiger konvertiert (siehe Abschnitt 6), sodass die zweite Deklaration p so initialisiert, dass sie auf das erste Element des unbenannten Arrays verweist.

Einige Compiler verfügen über einen Schalter, der steuert, ob Zeichenfolgenliterale beschreibbar sind oder nicht (zum Kompilieren von altem Code), und einige haben möglicherweise Optionen, um zu bewirken, dass Zeichenfolgenliterale formal als Arrays von const char behandelt werden (zur besseren Fehlererkennung).

Siehe auch Fragen 1.31, 6.1, 6.2, 6.8 und 11.8b.

Referenzen: K & R2 Sec. 5,5 p. 104

ISO Sec. 6.1.4, Kap. 6.5.7

Begründung Sec. 3.1.4

H & S Sec. 2.7.4 S. 31-2

JJJ
quelle
Warum zerfällt q in sizeof (q) nicht in einen Zeiger, wie @Jon in seiner Antwort erwähnt?
Garyp
@garyp q zerfällt nicht in einen Zeiger, da sizeof ein Operator und keine Funktion ist (selbst wenn sizeof eine Funktion wäre, würde q nur zerfallen, wenn die Funktion einen Zeichenzeiger erwartet).
GiriB
danke, aber printf ("% u \ n" anstelle von printf ("% zu \ n"), ich denke du solltest z entfernen.
Zakaria
33

Was ist der Unterschied zwischen char array und char pointer in C?

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 Zeichenkettenliteralen 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 "einfache" char-Array-Objekte sundt deren 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.

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

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
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
quelle
2
@ leszek.hanusz Undefiniertes Verhalten stackoverflow.com/questions/2766731/… Google "C language UB" ;-)
Ciro Santilli 3 冠状 病 六四 事件 3
9

Sie dürfen den Inhalt einer Zeichenfolgenkonstante nicht ändern, worauf der erste pPunkt hinweist. Das zweite pist ein Array, das mit einer Zeichenfolgenkonstante initialisiert wurde, und Sie können dessen Inhalt ändern.

Potrzebie
quelle
6

In solchen Fällen ist der Effekt derselbe: Am Ende übergeben Sie die Adresse des ersten Zeichens in einer Zeichenfolge.

Die Erklärungen sind jedoch offensichtlich nicht dieselben.

Im Folgenden wird der Speicher für eine Zeichenfolge und auch einen Zeichenzeiger reserviert und der Zeiger so initialisiert, dass er auf das erste Zeichen in der Zeichenfolge zeigt.

char *p = "hello";

Während das Folgende Speicher nur für die Zeichenfolge reserviert. Es kann also tatsächlich weniger Speicher verbrauchen.

char p[10] = "hello";
Jonathan Wood
quelle
codeplusplus.blogspot.com/2007/09/… "Das Initialisieren der Variablen erfordert jedoch einen enormen Leistungs- und Platzverlust für das Array"
leef
@leef: Ich denke, das hängt davon ab, wo sich die Variable befindet. Wenn es sich im statischen Speicher befindet, ist es meiner Meinung nach möglich, dass das Array und die Daten im EXE-Image gespeichert werden und überhaupt keine Initialisierung erfordern. Andernfalls kann es sicherlich langsamer sein, wenn die Daten zugewiesen und dann die statischen Daten kopiert werden müssen.
Jonathan Wood
3

Soweit ich mich erinnern kann, ist ein Array tatsächlich eine Gruppe von Zeigern. Beispielsweise

p[1]== *(&p+1)

ist eine wahre Aussage

CosminO
quelle
2
Ich würde ein Array als Zeiger auf die Adresse eines Speicherblocks beschreiben. Deshalb *(arr + 1)bringt Sie zum zweiten Mitglied von arr. Wenn *(arr)Punkte auf eine 32-Bit - Speicheradresse, zum Beispiel bfbcdf5e, dann *(arr + 1)weisen auf bfbcdf60(das zweite Byte). Daher führt das Verlassen des Bereichs eines Arrays zu seltsamen Ergebnissen, wenn das Betriebssystem keinen Fehler aufweist. Wenn int a = 24;ist an der Adressebfbcdf62 , wird der Zugriff arr[2]möglicherweise zurückgegeben 24, vorausgesetzt, ein Segfault tritt nicht zuerst auf.
Braden Best
3

Aus APUE , Abschnitt 5.14 :

char    good_template[] = "/tmp/dirXXXXXX"; /* right way */
char    *bad_template = "/tmp/dirXXXXXX";   /* wrong way*/

... Für die erste Vorlage wird der Name auf dem Stapel zugewiesen, da wir eine Array-Variable verwenden. Für den zweiten Namen verwenden wir jedoch einen Zeiger. In diesem Fall befindet sich nur der Speicher für den Zeiger selbst auf dem Stapel. Der Compiler sorgt dafür, dass die Zeichenfolge im schreibgeschützten Segment der ausführbaren Datei gespeichert wird. Wenn die mkstempFunktion versucht, die Zeichenfolge zu ändern, tritt ein Segmentierungsfehler auf.

Der zitierte Text entspricht der Erklärung von @Ciro Santilli.

Rick
quelle
1

char p[3] = "hello"? Denken Sie char p[6] = "hello"daran, dass am Ende eines "Strings" in C ein '\ 0'-Zeichen steht.

Auf jeden Fall ist das Array in C nur ein Zeiger auf das erste Objekt eines Anpassungsobjekts im Speicher. Die einzigen Unterschiede sind in der Semantik. Während Sie den Wert eines Zeigers so ändern können, dass er auf eine andere Position im Speicher zeigt, zeigt ein Array nach seiner Erstellung immer auf dieselbe Position.
Auch bei Verwendung des Arrays werden "Neu" und "Löschen" automatisch für Sie erledigt.

Roee Gavirel
quelle