Sind Char-Arrays garantiert nullterminiert?

Lesezeit: 10 Minuten

Dans Benutzeravatar
Dan

#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?

  • 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


Benutzeravatar von dbush
dbusch

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

  • @ Dan Ja. Aber es ist besser, einfach zu schreiben char b[] = "hi";

    – Betrüger

    13. September 2021 um 12:30 Uhr

  • @Dan: Lustige Tatsache: das ist in C++ anders. Es ist in C++ nicht legal, die explizite Größe zu klein zu machen, um ein abschließendes 0-Byte aufzunehmen, wenn ein Initialisierer mit doppelten Anführungszeichen verwendet wird. Wenn Sie das also in C++ wollen, müssen Sie schreiben char b[] = {'h', 'i'}; Ärgerlich manchmal für SIMD-Lookup-Tabellen, zB static char hex_lut[16] = "0123...ef"; benötigt eine 17. Byte oder weniger lesbare Quelle für den Initialisierer in C++. Beispiel mit GCC im C vs. C++ Modus, keine Warnungen vs. eine Fehlermeldung. godbolt.org/z/eTx94a4h7

    – Peter Cordes

    14. September 2021 um 5:27 Uhr


  • @Cheatah, es ist wahrscheinlich nicht der Compiler, der die Warnung hinzufügt, sondern nur die Codezeile wo zeigt b definiert ist, und der Text “kein Raum” war in einem Kommentar vorhanden.

    – Ilkkachu

    14. September 2021 um 9:15 Uhr

  • @jamesqf Nein, das ist nie besser. Mach es wenigstens const. Und ob ein Zeiger auf const char ist besser als ein Array, es hängt natürlich von der Verwendung ab, da es nicht geändert werden kann.

    – Konrad Rudolf

    14. September 2021 um 11:31 Uhr


  • @RDragonrydr Wenn ein Zeichenfolgenliteral zum Initialisieren eines Arrays verwendet wird, wird sein Inhalt (bis zur Größe des Arrays) in das Array kopiert. Das Zeichenfolgenliteral selbst kann auch separat im Speicher erscheinen, abhängig vom einzelnen Compiler und davon, ob die Zeichenfolgenkonstante an anderer Stelle verwendet wird.

    – dbusch

    14. September 2021 um 19:27 Uhr

Benutzeravatar von Steve Summit
Steve Gipfel

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 b0geben Sie keine Größe an, sodass der Compiler den Zeichenfolgeninitialisierer verwendet, um die richtige Größe auszuwählen, die 3 sein wird.

Zum b1geben Sie eine Größe an, die jedoch zu klein ist, sodass der Compiler Ihnen einen Fehler ausgeben sollte.

Zum b2was 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 b3geben 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.)

  • Die Reihenfolge, in der ein Compiler Variablen speichert, wird oft (obwohl vom Compiler abhängig) so gewählt, dass Platzverschwendung zwischen Variablen vermieden wird. Eine 4-Byte- oder größere Variable und in einigen Fällen Arrays müssen an einer Adresse beginnen, die ein Vielfaches von 4 ist. Wenn Sie also 2-Byte- oder 1-Byte-Variablen in der Nähe haben, werden sie möglicherweise neu geordnet, um dies zu vermeiden Verschwendete Bytes. (Die Größe verschiedener Variablentypen ist natürlich auch Compiler-abhängig. In diesem Fall ist die char ist in vielen Compilern nur 1 oder 2 Bytes, also können sie damit verschoben werden b kann mit einem Vielfachen von 4 beginnen.)

    – Darrel Hoffmann

    14. September 2021 um 15:21 Uhr

  • Kleiner Nitpick: “C erstellt automatisch …”, da C technisch gesehen nicht die gleiche Art von Laufzeit hat wie eine interpretierte Sprache, ist es der Compiler / Optimierer, der aus C übersetzt und asm / obj ausgibt (dh alles passiert vor jeder Ausführung) und dabei die von den Standards vorgegebene Syntax einhalten, so dass es möglicherweise nicht garantiert ist, dass es in einem standardmäßig unvollständigen Compiler (z. B. einigen Golf-CCs) funktioniert. Wenn ein solcher Code in einer Binärdatei erreicht wird, befindet er sich bereits in einem endlichen Zustand und C existiert zu diesem Zeitpunkt nicht mehr.

    – Peter Badida

    15. September 2021 um 19:46 Uhr

Benutzeravatar von 0___________
0___________

Es gibt zwei Dinge in Ihrer Frage.

  1. 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
    
  2. 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.

1411900cookie-checkSind Char-Arrays garantiert nullterminiert?

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

Privacy policy