Wie sollen Zeichenarrays als Zeichenfolgen verwendet werden?

10

Ich verstehe, dass Strings in C nur Zeichenarrays sind. Also habe ich den folgenden Code ausprobiert, aber er liefert seltsame Ergebnisse, wie z. B. Müllausgabe oder Programmabstürze:

#include <stdio.h>

int main (void)
{
  char str [5] = "hello";
  puts(str);
}

Warum funktioniert das nicht?

Es kompiliert sauber mit gcc -std=c17 -pedantic-errors -Wall -Wextra.


Hinweis: Dieser Beitrag ist als kanonische FAQ für Probleme gedacht, die darauf zurückzuführen sind, dass beim Deklarieren einer Zeichenfolge kein Platz für einen NUL-Terminator zugewiesen wurde.

Lundin
quelle

Antworten:

12

AC-Zeichenfolge ist ein Zeichenarray, das mit einem Nullterminator endet .

Alle Zeichen haben einen Symboltabellenwert. Der Nullterminator ist der Symbolwert 0(Null). Es wird verwendet, um das Ende einer Zeichenfolge zu markieren. Dies ist erforderlich, da die Größe der Zeichenfolge nirgendwo gespeichert wird.

Daher müssen Sie jedes Mal, wenn Sie Platz für eine Zeichenfolge zuweisen, ausreichend Platz für das Null-Abschlusszeichen einfügen. Ihr Beispiel tut dies nicht, es weist nur Platz für die 5 Zeichen von zu "hello". Richtiger Code sollte sein:

char str[6] = "hello";

Oder Sie können einen selbstdokumentierenden Code für 5 Zeichen plus 1 Nullterminator schreiben:

char str[5+1] = "hello";

Wenn Sie zur Laufzeit dynamisch Speicher für eine Zeichenfolge zuweisen, müssen Sie auch Platz für den Nullterminator zuweisen:

char input[n] = ... ;
...
char* str = malloc(strlen(input) + 1);

Wenn Sie am Ende einer Zeichenfolge keinen Nullterminator anhängen, funktionieren Bibliotheksfunktionen, die eine Zeichenfolge erwarten, nicht ordnungsgemäß und es treten Fehler mit "undefiniertem Verhalten" auf, z. B. Müllausgabe oder Programmabstürze.

Die gebräuchlichste Methode zum Schreiben eines Null-Abschlusszeichens in C ist die Verwendung einer sogenannten "oktalen Escape-Sequenz", die folgendermaßen aussieht : '\0'. Dies entspricht zu 100% dem Schreiben 0, \dient jedoch als selbstdokumentierender Code, um anzugeben, dass die Null explizit als Nullterminator gedacht ist. Code wie if(str[i] == '\0')prüft, ob das spezifische Zeichen der Nullterminator ist.

Bitte beachten Sie, dass der Begriff Nullterminator nichts mit Nullzeigern oder dem NULLMakro zu tun hat ! Dies kann verwirrend sein - sehr ähnliche Namen, aber sehr unterschiedliche Bedeutungen. Aus diesem Grund wird der Nullterminator manchmal als NULmit einem L bezeichnet, nicht zu verwechseln mit NULLoder Nullzeigern. Weitere Informationen finden Sie in den Antworten auf diese SO-Frage .

Das "hello"in Ihrem Code wird als String-Literal bezeichnet . Dies ist als schreibgeschützte Zeichenfolge anzusehen. Die ""Syntax bedeutet, dass der Compiler am Ende des Zeichenfolgenliteral automatisch einen Nullterminator anfügt. Wenn Sie also ausdrucken, erhalten sizeof("hello")Sie 6, nicht 5, da Sie die Größe des Arrays einschließlich eines Nullterminators erhalten.


Es kompiliert sauber mit gcc

In der Tat nicht einmal eine Warnung. Dies liegt an einem subtilen Detail / Fehler in der C-Sprache, der es ermöglicht, Zeichenarrays mit einem Zeichenfolgenliteral zu initialisieren, das genau so viele Zeichen enthält, wie Platz im Array vorhanden ist, und dann den Nullterminator stillschweigend zu verwerfen (C17 6.7.9 / fünfzehn). Die Sprache verhält sich aus historischen Gründen absichtlich so. Weitere Informationen finden Sie unter Inkonsistente gcc-Diagnose für die Zeichenfolgeninitialisierung . Beachten Sie auch, dass C ++ hier anders ist und die Verwendung dieses Tricks / Fehlers nicht zulässt.

Lundin
quelle
1
Sie sollten den char str[] = "hello";Fall erwähnen .
Jabberwocky
@Jabberwocky Dies ist ein Community-Wiki. Sie können es jederzeit bearbeiten und Beiträge leisten.
Lundin
1
... und vielleicht auch das char *str = "hello";... str[0] = foo;Problem.
Jabberwocky
Erweitern Sie möglicherweise die Implikation der Verwendung sizeofauf die Verwendung für einen Funktionsparameter, insbesondere wenn dieser als Array definiert ist.
Wetterfahne
@WeatherVane Sollte durch eine andere FAQ hier behandelt werden: stackoverflow.com/questions/492384/…
Lundin
4

Aus dem C-Standard (7.1.1 Begriffsbestimmungen)

1 Eine Zeichenfolge ist eine zusammenhängende Folge von Zeichen, die mit dem ersten Nullzeichen abgeschlossen sind und dieses enthalten.Der Begriff Multibyte-Zeichenfolge wird manchmal verwendet, um die spezielle Verarbeitung von Multibyte-Zeichen in der Zeichenfolge hervorzuheben oder Verwechslungen mit einer breiten Zeichenfolge zu vermeiden. Ein Zeiger auf eine Zeichenfolge ist ein Zeiger auf das ursprüngliche Zeichen (niedrigste Adresse). Die Länge einer Zeichenfolge ist die Anzahl der Bytes vor dem Nullzeichen, und der Wert einer Zeichenfolge ist die Reihenfolge der Werte der enthaltenen Zeichen in der angegebenen Reihenfolge.

In dieser Erklärung

char str [5] = "hello";

Das String-Literal "hello"hat die interne Darstellung wie

{ 'h', 'e', 'l', 'l', 'o', '\0' }

es hat also 6 Zeichen einschließlich der abschließenden Null. Seine Elemente werden verwendet, um das Zeichenarray zu initialisierenstr nur Platz für 5 Zeichen reserviert.

Der C-Standard (im Gegensatz zum C ++ - Standard) ermöglicht eine solche Initialisierung eines Zeichenarrays, wenn die abschließende Null eines Zeichenfolgenliteral nicht als Initialisierer verwendet wird.

Infolgedessen enthält das Zeichenarray strjedoch keine Zeichenfolge.

Wenn Sie möchten, dass das Array eine Zeichenfolge enthält, können Sie schreiben

char str [6] = "hello";

oder nur

char str [] = "hello";

Im letzten Fall wird die Größe des Zeichenarrays aus der Anzahl der Initialisierer des Zeichenfolgenliteral bestimmt, die gleich 6 ist.

Vlad aus Moskau
quelle
0

Können alle Zeichenfolgen als Zeichenarray betrachtet werden ( Ja ), können alle Zeichenarrays als Zeichenfolgen betrachtet werden ( Nein)? )?

Warum nicht? und warum ist das wichtig?

Zusätzlich zu den anderen Antworten, die erklären, dass die Länge einer Zeichenfolge nirgendwo als Teil der Zeichenfolge gespeichert ist, und den Verweisen auf den Standard, in dem eine Zeichenfolge definiert ist, lautet die Kehrseite: "Wie behandeln die Funktionen der C-Bibliothek Zeichenfolgen?"

Während ein Zeichenarray dieselben Zeichen enthalten kann, handelt es sich lediglich um ein Zeichenarray, es sei denn, auf das letzte Zeichen folgt das nicht abschließende Zeichen. Das nicht endende Zeichen ermöglicht es, das Array von Zeichen als Zeichenfolge zu betrachten (zu behandeln).

Alle Funktionen in C, die eine Zeichenfolge als Argument erwarten, erwarten, dass die Zeichenfolge nicht abgeschlossen wird . Warum?

Dies hängt mit der Funktionsweise aller Zeichenfolgenfunktionen zusammen. Da die Länge nicht Teil eines Arrays ist, scannen Zeichenfolgenfunktionen im Array vorwärts, bis das Nullzeichen (z. B. '\0'- entspricht der Dezimalzahl 0) gefunden wird. Siehe ASCII-Tabelle und Beschreibung . Egal , ob Sie mit strcpy, strchr, strcspn, etc .. Alle verlassen String - Funktionen auf dem nul-Abschluss anwesend Charakter zu definieren , wo das Ende dieser Zeichenfolge ist.

Ein Vergleich zweier ähnlicher Funktionen aus string.hwird die Bedeutung des nicht terminierenden Zeichens hervorheben . Nehmen Sie zum Beispiel:

    char *strcpy(char *dest, const char *src);

Die strcpyFunktion kopiert einfach Bytes von srcbis, destbis das nicht abschließende Zeichen gefunden wird, das angibt , strcpywo das Kopieren von Zeichen beendet werden soll. Nehmen Sie nun die ähnliche Funktion memcpy:

    void *memcpy(void *dest, const void *src, size_t n);

Die Funktion führt eine ähnliche Operation aus, berücksichtigt oder erfordert jedoch nicht, dass der srcParameter eine Zeichenfolge ist. Da memcpybeim srcKopieren von Bytes nicht einfach vorwärts gescannt werden kann, destbis ein nicht abschließendes Zeichen erreicht ist, ist eine explizite Anzahl von Bytes zum Kopieren als dritter Parameter erforderlich. Dieser dritte Parameter liefert memcpymit der gleichen Größe Informationen strcpy, die einfach durch Vorwärtsscannen abgeleitet werden können, bis ein nicht terminierendes Zeichen gefunden wird.

(was auch betont, was in strcpy(oder einer Funktion, die eine Zeichenfolge erwartet) schief geht, wenn Sie der Funktion keine nicht terminierte Zeichenfolge zur Verfügung stellen - sie hat keine Ahnung, wo sie anhalten soll, und rast glücklich über den Rest Ihres Speichersegments davon Aufrufen von undefiniertem Verhalten, bis ein Nullzeichen zufällig irgendwo im Speicher gefunden wird - oder ein Segmentierungsfehler auftritt)

Aus diesem Grund muss Funktionen, die eine nicht terminierte Zeichenfolge erwarten, eine nicht terminierte Zeichenfolge übergeben werden, und warum dies wichtig ist .

David C. Rankin
quelle
0

Intuitiv ...

Stellen Sie sich ein Array als Variable vor (enthält Dinge) und eine Zeichenfolge als Wert (kann in eine Variable eingefügt werden).

Sie sind sicherlich nicht dasselbe. In Ihrem Fall ist die Variable zu klein, um die Zeichenfolge aufzunehmen, sodass die Zeichenfolge abgeschnitten wird. ("Anführungszeichen" in C haben am Ende ein implizites Nullzeichen.)

Es ist jedoch möglich, eine Zeichenfolge in einem Array zu speichern, das viel größer als die Zeichenfolge ist.

Beachten Sie, dass die üblichen Zuweisungs- und Vergleichsoperatoren ( = == <usw.) nicht wie erwartet funktionieren. Aber die strxyzFunktionsfamilie kommt ziemlich nahe, sobald Sie wissen, was Sie tun. Weitere Informationen finden Sie in den C-FAQ zu Zeichenfolgen und Arrays .

Artelius
quelle