#include <stdio.h>
int main() {
char a = 5;
char b[2] = "hi"; // No explicit room for `\0`.
char c = 6;
return 0;
}
Immer wenn wir einen String schreiben, eingeschlossen in doppelte Anführungszeichen, erstellt C automatisch ein Array von Zeichen für uns, das diesen String enthält, abgeschlossen durch das Zeichen \0
http://www.eskimo.com/~scs/cclass/notes/sx8.html
Im obigen Beispiel b
hat nur Platz für 2 Zeichen, sodass das Null-Endzeichen keine Stelle hat, an der es platziert werden kann, und dennoch organisiert der Compiler die Speicheranweisungen so neu a
und c
werden vorher gespeichert b
in Erinnerung, um Platz zu machen für a \0
am Ende des Arrays.
Ist dies zu erwarten oder treffe ich auf undefiniertes Verhalten?
Es ist erlaubt, a zu initialisieren char
Array mit einem String, wenn das Array mindestens groß genug ist, um alle Zeichen des Strings aufzunehmen Außerdem das Null-Terminator.
Dies wird in Abschnitt 6.7.9p14 des ausführlich beschrieben C-Standard:
Ein Array vom Zeichentyp kann durch ein Zeichenfolgenliteral oder ein UTF-8-Zeichenfolgenliteral initialisiert werden, optional eingeschlossen in geschweiften Klammern. Aufeinanderfolgende Bytes des Zeichenfolgenliterals (einschließlich des abschließenden Nullzeichens, wenn Platz vorhanden ist oder das Array eine unbekannte Größe hat) initialisieren die Elemente des Arrays.
Dies bedeutet jedoch auch, dass Sie das Array nicht als Zeichenfolge behandeln können, da es nicht nullterminiert ist. Also wie geschrieben, da bist du nicht Durchführen von Zeichenfolgenoperationen an b
dein Code ist in Ordnung.
Was du kippen do ist mit einer Zeichenfolge initialisieren, die zu lang ist, dh:
char b[2] = "hello";
Da dies mehr Initialisierer ergibt, als in das Array passen, und eine Einschränkungsverletzung darstellt. Abschnitt 6.7.9p2 besagt dies wie folgt:
Kein Initialisierer darf versuchen, einen Wert für ein Objekt bereitzustellen, das nicht in der zu initialisierenden Entität enthalten ist.
Wenn Sie das Array wie folgt deklarieren und initialisieren würden:
char b[] = "hi";
Dann b
wäre ein Array der Größe 3, das groß genug ist, um die beiden Zeichen in der Zeichenfolge konstant zu halten, plus das abschließende Null-Byte b
ein Faden.
Zusammenfassen:
Wenn das Array eine feste Größe hat:
- Wenn die zum Initialisieren verwendete String-Konstante kürzer als das Array ist, enthält das Array die Zeichen im String, wobei aufeinanderfolgende Elemente auf 0 gesetzt sind, sodass das Array einen String enthält.
- Wenn das Array genau groß genug ist, um die Elemente des Strings aufzunehmen, aber nicht das Null-Terminator, enthält das Array die Zeichen in der Zeichenfolge ohne das Null-Terminator, was bedeutet, dass das Array kein String ist.
- Wenn die String-Konstante (ohne Berücksichtigung des Null-Terminators) länger als das Array ist, ist dies eine Einschränkungsverletzung, die ausgelöst wird undefiniertes Verhalten
Wenn das Array keine explizite Größe hat, wird die Größe des Arrays so angepasst, dass es die Zeichenfolgenkonstante plus das abschließende Nullbyte enthält.
Immer wenn wir einen String schreiben, eingeschlossen in doppelte Anführungszeichen, erstellt C automatisch ein Array von Zeichen für uns, das diesen String enthält, abgeschlossen durch das Zeichen \0.
Diese Hinweise sind in diesem Fall leicht irreführend. Ich werde sie aktualisieren müssen.
Wenn du so etwas schreibst
char *p = "Hello";
oder
printf("world!\n");
C erstellt automatisch ein Array von Zeichen für Sie, das genau die richtige Größe hat und die Zeichenfolge enthält, die durch das abgeschlossen wird \0
Charakter.
Im Fall von Array-Initialisierern sind die Dinge jedoch etwas anders. Wenn du schreibst
char b[2] = "hi";
Die Zeichenfolge ist lediglich der Initialisierer für ein Array, das Sie erschaffen. So haben Sie die volle Kontrolle über die Größe. Es gibt mehrere Möglichkeiten:
char b0[] = "hi"; // compiler infers size
char b1[1] = "hi"; // error
char b2[2] = "hi"; // No terminating 0 in the array. (Illegal in C++, BTW)
char b3[3] = "hi"; // explicit size matches string literal
char b4[10] = "hi"; // space past end of initializer is always zero-initialized
Zum b0
geben Sie keine Größe an, sodass der Compiler den Zeichenfolgeninitialisierer verwendet, um die richtige Größe auszuwählen, die 3 sein wird.
Zum b1
geben Sie eine Größe an, die jedoch zu klein ist, sodass der Compiler Ihnen einen Fehler ausgeben sollte.
Zum b2
was der Fall ist, nach dem Sie gefragt haben, geben Sie eine Größe an, die gerade noch groß genug für die expliziten Zeichen im String-Initialisierer ist, aber nicht das Beenden \0
. Dies ist ein Sonderfall. Es ist legal, aber was Sie am Ende dabei haben b2
ist kein richtiger nullterminierter String. Da es bestenfalls ungewöhnlich ist, gibt der Compiler möglicherweise eine Warnung aus. Weitere Informationen zu diesem Fall finden Sie in dieser Frage.
Zum b3
geben Sie eine genau richtige Größe an, sodass Sie eine richtige Zeichenfolge in einem Array mit exakter Größe erhalten, genau wie b0
.
Zum b4
, geben Sie eine zu große Größe an, obwohl dies kein Problem darstellt. Es entsteht zusätzlicher Platz im Array, jenseits der Terminierung \0
. (Tatsächlich wird dieser zusätzliche Platz auch gefüllt \0
.) Mit diesem zusätzlichen Platz können Sie sicher so etwas tun wie strcat(b4, ", wrld!")
.
Unnötig zu erwähnen, dass Sie die meiste Zeit verwenden möchten b0
bilden. Das Zählen von Zeichen ist mühsam und fehleranfällig. Wie Brian Kernighan (einer der Schöpfer von C) in diesem Zusammenhang geschrieben hat: „Lassen Sie den Computer die Drecksarbeit erledigen.“
Eine Sache noch. Sie schrieben:
und doch reorganisiert der Compiler die Speicherbefehle so, dass a
und c
werden vorher gespeichert b
in Erinnerung, um Platz zu machen für a \0
am Ende des Arrays.
Ich weiß nicht, was dort vor sich geht, aber man kann mit Sicherheit sagen, dass der Compiler es ist nicht versuchen, “Platz zu machen für a \0
“. Compiler können und tun dies oft in ihrer eigenen unergründlichen internen Reihenfolge, die weder der Reihenfolge entspricht, in der Sie sie deklariert haben, noch der alphabetischen Reihenfolge oder irgendetwas anderem, das Ihnen einfällt. Wenn unter Ihrem Compiler-Array b
endete mit zusätzlichem Leerzeichen danach, das a enthielt \0
wie um die Saite zu beenden, das war wohl im Grunde Zufall, nicht weil der Compiler versucht hat, nett zu dir zu sein und dabei zu helfen, so etwas zu machen printf("%s\n", b)
besser definiert werden. (Unter den beiden Compilern, wo ich es ausprobiert habe, printf("%s\n", b)
gedruckt hi^E
und hi ??
die wie erwartet deutlich das Vorhandensein von nachgestelltem zufälligem Müll zeigt.)
Es gibt zwei Dinge in Ihrer Frage.
-
Zeichenfolgenliteral. Das Zeichenfolgenliteral (dh etwas, das in doppelte Anführungszeichen eingeschlossen ist) ist immer die korrekte, mit Nullzeichen abgeschlossene Zeichenfolge.
char *p = "ABC"; // p references null character terminated string
-
Das Zeichenarray kann nur so viele Elemente enthalten, wie es hat, wenn Sie also versuchen, ein Array mit zwei Elementen zu initialisieren drei Elemente String-Literal, nur zwei erste werden geschrieben. Das Array enthält also nicht die mit Nullzeichen abgeschlossene C-Zeichenfolge
char p[2] = "AB"; // p is not a valid C string.
Ein Array von char muss überhaupt nicht abgeschlossen werden. Es ist ein Array. Wenn der tatsächliche Inhalt kleiner als die Abmessungen des Arrays ist, müssen Sie die Größe dieses Inhalts verfolgen.
Die Antworten scheinen hier zu einer String-Diskussion verkommen zu sein. Nicht alle char-Arrays sind Strings. Es ist jedoch eine sehr strenge Konvention, ein Null-Terminator als Wächter zu verwenden, wenn sie de facto als Zeichenfolgen behandelt werden sollen.
Ihr Array kann etwas anderes verwenden und kann auch Trennzeichen und Zonen haben. Schließlich kann es eine Union sein oder eine Struktur überlagern. Möglicherweise ein Bereitstellungsbereich für ein anderes System.
Zum Satz über die Reihenfolge der Speicherung: Es ist kein “Platzmachen für den Terminator”. Es gibt keine, und der Compiler kann die Variablen beliebig speichern.
– Wetterfahne
13. September 2021 um 12:24 Uhr
Das Zeichenfolgenliteral wird erstellt, und dieses Zeichenfolgenliteral enthält das Null-Terminatorzeichen. Zur Laufzeit das Array
b
wird mit den ersten 2 Zeichen aus dem String-Literal initialisiert, enthält aber kein Null-Terminator. (b
ist keine Zeichenfolge).– William Pursel
13. September 2021 um 12:27 Uhr
Ein String in C ist ein NULL-terminiertes Char-Array, also wenn es nicht NULL-terminiert ist, ist es kein String … nur ein Char-Array. Viele der String-Funktionen suchen nach dem NULL-Zeichen (z. B. um zu wissen, wann es aufhören soll, Zeichen von einem String in einen anderen zu kopieren), also werden sie ohne es nicht richtig funktionieren (z. B. so lange Zeichen kopieren, bis sie auf welche stoßen zufälliges NULL-Zeichen irgendwo im Speicher).
– Baard Kopperud
13. September 2021 um 20:30 Uhr
Nicht genau dieselbe Frage stellen, aber diese Frage vollständig beantworten (zählt das als Dupe?), weil beide auf derselben Verwirrung basieren: Wie initialisiert man ein char-Array ohne das Null-Terminator?
– ShadowRanger
14. September 2021 um 3:21 Uhr
Dies ist kein “String-Array”, das wäre
char *array_of_strings[] = {"hi", "mom"};
. Sie können es einen String nennen (wenn es ein 0-Endzeichen hat, auch bekannt als ASCII nul (nicht NULL, @Baard)), oder Sie können es ein char-Array nennen.– Peter Cordes
14. September 2021 um 5:20 Uhr