Ist das Initialisieren eines Zeichens [] mit einem String-Literal eine schlechte Übung?

44

Ich habe einen Thread mit dem Titel "strlen vs sizeof" auf CodeGuru gelesen , und eine der Antworten besagt, dass "es sowieso eine schlechte Praxis ist, ein charArray mit einem String-Literal zu initialisieren ".

Stimmt das, oder ist das nur seine (wenn auch "Elite-Mitglied") Meinung?


Hier ist die ursprüngliche Frage:

#include <stdio.h>
#include<string.h>
main()
{
    char string[] = "october";
    strcpy(string, "september");

    printf("the size of %s is %d and the length is %d\n\n", string, sizeof(string), strlen(string));
    return 0;
}

richtig. die größe sollte die länge plus 1 sein ja?

das ist die Ausgabe

the size of september is 8 and the length is 9

größe sollte sicher 10 sein. Es ist wie das Berechnen der Zeichenfolgengröße, bevor es von strcpy geändert wird, aber die Länge danach.

Stimmt etwas mit meiner Syntax nicht oder was?


Hier ist die Antwort :

Es ist sowieso eine schlechte Praxis, ein Zeichen-Array mit einem String-Literal zu initialisieren. Führen Sie daher immer einen der folgenden Schritte aus:

const char string1[] = "october";
char string2[20]; strcpy(string2, "september");
Cole Johnson
quelle
Beachten Sie die "const" in der ersten Zeile. Könnte es sein, dass der Autor c ++ anstelle von c angenommen hat? In c ++ ist es "schlechte Praxis", da ein Literal const sein sollte und jeder neuere c ++ - Compiler eine Warnung (oder einen Fehler) darüber ausgibt, dass einem Nicht-const-Array ein const-Literal zugewiesen wird.
André
@ André C ++ definiert String-Literale als const-Arrays, da dies der einzig sichere Weg ist, mit ihnen umzugehen. Das C ist nicht das Problem, so dass Sie eine soziale Regel haben, die die sichere Sache erzwingt
Caleth
@Caleth. Ich weiß, ich habe mehr versucht zu argumentieren, dass der Autor der Antwort sich der "schlechten Praxis" aus einer C ++ - Perspektive näherte.
André
@ André es ist keine schlechte Übung in C ++, weil es keine Übung ist , es ist ein direkter Tippfehler. Es sollte ein Tippfehler in C sein, ist es aber nicht, sodass Sie eine
Styleguide-

Antworten:

59

Es ist sowieso eine schlechte Praxis, ein Zeichen-Array mit einem String-Literal zu initialisieren.

Der Autor dieses Kommentars rechtfertigt es nie wirklich, und ich finde die Aussage rätselhaft.

In C (und Sie dies als C markiert haben), das ist so ziemlich der einzige Weg zu initialisieren ein Array von charmit einem String - Wert (Initialisierung unterscheidet sich von Zuordnung). Sie können entweder schreiben

char string[] = "october";

oder

char string[8] = "october";

oder

char string[MAX_MONTH_LENGTH] = "october";

Im ersten Fall wird die Größe des Arrays von der Größe des Initialisierers abgeleitet. String-Literale werden als Arrays charmit einem abschließenden 0-Byte gespeichert , sodass die Größe des Arrays 8 ist ('o', 'c', 't', 'o', 'b', 'e', ​​'r', 0). In den zweiten beiden Fällen wird die Größe des Arrays als Teil der Deklaration angegeben (8 und MAX_MONTH_LENGTHwas auch immer das sein mag).

Was Sie nicht tun können, ist etwas zu schreiben

char string[];
string = "october";

oder

char string[8];
string = "october";

usw. Im ersten Fall, der die Erklärung stringist unvollständig , da keine Array - Größe angegeben wurde und es gibt keine Initialisierer von der Größe zu nehmen. In beiden Fällen =funktioniert das nicht, weil a) ein Array-Ausdruck, wie er stringmöglicherweise nicht das Ziel einer Zuweisung ist, und b) der =Operator ohnehin nicht definiert ist, den Inhalt eines Arrays in ein anderes zu kopieren.

Aus dem gleichen Grund können Sie nicht schreiben

char string[] = foo;

wo fooist ein anderes Array von char. Diese Form der Initialisierung funktioniert nur mit String-Literalen.

BEARBEITEN

Ich sollte dies dahingehend ändern, dass Sie Arrays auch so initialisieren können, dass sie eine Zeichenfolge mit einem Initialisierer im Array-Stil enthalten, z

char string[] = {'o', 'c', 't', 'o', 'b', 'e', 'r', 0};

oder

char string[] = {111, 99, 116, 111, 98, 101, 114, 0}; // assumes ASCII

Aber es ist für die Augen einfacher, String-Literale zu verwenden.

BEARBEITEN 2

Um den Inhalt eines Arrays außerhalb einer Deklaration zuzuweisen , müssen Sie entweder strcpy/strncpy(für Zeichenfolgen mit 0-Abschluss) oder memcpy(für jeden anderen Array-Typ) Folgendes verwenden:

if (sizeof string > strlen("october"))
  strcpy(string, "october");

oder

strncpy(string, "october", sizeof string); // only copies as many characters as will
                                           // fit in the target buffer; 0 terminator
                                           // may not be copied, but the buffer is
                                           // uselessly completely zeroed if the
                                           // string is shorter!
John Bode
quelle
@KeithThompson: nicht widersprechen, nur der Vollständigkeit halber hinzugefügt.
John Bode
16
Bitte beachten Sie, dass dies char[8] str = "october";eine schlechte Praxis ist. Ich hatte buchstäblich char mich zu zählen , um sicherzustellen , es war kein Überlauf und es bricht unter Wartung ... zB einen Rechtschreibfehler zu korrigieren von brechen , wenn Größe nicht aktualisiert. seprateseparate
Djechlin
1
Ich bin mit Djechlin einverstanden, es ist aus den angegebenen Gründen eine schlechte Praxis. Die Antwort von JohnBode kommentiert überhaupt nicht den Aspekt der "schlechten Praxis" (was der Hauptteil der Frage ist !!), sondern erklärt nur, was Sie tun können oder nicht, um das Array zu initialisieren.
mastov
Minor: Als ‚Länge“ Wert zurückgegeben von strlen()nicht das Nullzeichen enthält, mit MAX_MONTH_LENGTHder maximalen Größe für erforderlich hält char string[]oft sieht . Falsch IMO, MAX_MONTH_SIZEwäre hier besser sein.
CHUX - wieder einzusetzen Monica
10

Das einzige Problem, an das ich mich erinnere, ist das Zuweisen von Zeichenfolgenliteral zu char *:

char var1[] = "september";
var1[0] = 'S'; // Ok - 10 element char array allocated on stack
char const *var2 = "september";
var2[0] = 'S'; // Compile time error - pointer to constant string
char *var3 = "september";
var3[0] = 'S'; // Modifying some memory - which may result in modifying... something or crash

Nehmen Sie zum Beispiel dieses Programm:

#include <stdio.h>

int main() {
  char *var1 = "september";
  char *var2 = "september";
  var1[0] = 'S';
  printf("%s\n", var2);
}

Dies stürzt auf meiner Plattform (Linux) ab, wenn versucht wird, auf eine Seite zu schreiben, die als schreibgeschützt markiert ist. Auf anderen Plattformen wird möglicherweise "September" usw. gedruckt.

Das heißt, die Initialisierung durch Literal bewirkt, dass der bestimmte Reservierungsbetrag nicht funktioniert:

char buf[] = "May";
strncpy(buf, "September", sizeof(buf)); // Result "Sep"

Aber das wird es

char buf[32] = "May";
strncpy(buf, "September", sizeof(buf));

Als letzte Bemerkung - würde ich gar nicht verwenden strcpy:

char buf[8];
strcpy(buf, "very long string very long string"); // Oops. We overwrite some random memory

Während einige Compiler es in einen sicheren Aufruf umwandeln können, ist es strncpyviel sicherer:

char buf[1024];
strncpy(buf, something_else, sizeof(buf)); // Copies at most sizeof(buf) chars so there is no possibility of buffer overrun. Please note that sizeof(buf) works for arrays but NOT pointers.
buf[sizeof(buf) - 1] = '\0';
Maciej Piechotka
quelle
Es besteht immer noch das Risiko eines Pufferüberlaufs, strncpyda der kopierte String nicht durch null beendet wird, wenn length of something_elsegrößer als ist sizeof(buf). Normalerweise lege ich das letzte Zeichen fest buf[sizeof(buf)-1] = 0, um es davor zu schützen, oder bufverwende es sizeof(buf) - 1als Kopierlänge, wenn es auf Null gesetzt ist .
Syockit
Verwenden Sie strlcpyoder strcpy_soder sogar, snprintfwenn Sie müssen.
user253751
Fest. Leider gibt es keine einfache tragbare Möglichkeit, dies zu tun, es sei denn, Sie haben den Luxus, mit den neuesten Compilern zu arbeiten ( strlcpyund snprintfsind auf MSVC nicht direkt verfügbar, zumindest bei Bestellungen und strcpy_snicht auf * nix).
Maciej Piechotka
@MaciejPiechotka: Gott sei Dank hat Unix den von Microsoft gesponserten Anhang k abgelehnt.
Deduplikator
6

Eine Sache, die keiner der Themen aufwirft, ist die folgende:

char whopping_great[8192] = "foo";

gegen

char whopping_great[8192];
memcpy(whopping_great, "foo", sizeof("foo"));

Ersterer wird so etwas tun wie:

memcpy(whopping_great, "foo", sizeof("foo"));
memset(&whopping_great[sizeof("foo")], 0, sizeof(whopping_great)-sizeof("foo"));

Letzteres macht nur die memcpy. Der C-Standard besteht darauf, dass, wenn ein Teil eines Arrays initialisiert wird, dies auch der Fall ist. In diesem Fall ist es also besser, es selbst zu tun. Ich denke, das war vielleicht das, worauf Treuss abzielte.

Sicher

char whopping_big[8192];
whopping_big[0] = 0;

ist besser als entweder:

char whopping_big[8192] = {0};

oder

char whopping_big[8192] = "";

ps Für Bonuspunkte können Sie Folgendes tun:

memcpy(whopping_great, "foo", (1/(sizeof("foo") <= sizeof(whopping_great)))*sizeof("foo"));

um eine durch Null geteilte Kompilierzeit zu erhalten, wenn das Array überlaufen soll.

Richard Fife
quelle
5

In erster Linie, weil Sie nicht die Größe von char[]in einer Variablen / einem Konstrukt haben, die / das Sie einfach im Programm verwenden können.

Das Codebeispiel aus dem Link:

 char string[] = "october";
 strcpy(string, "september");

stringwird auf dem Stapel mit einer Länge von 7 oder 8 Zeichen zugewiesen. Ich kann mich nicht erinnern, ob es auf diese Weise nullterminiert wurde oder nicht - der Thread, mit dem Sie verlinkt haben, hat dies bestätigt.

Das Kopieren von "September" über diesen String ist ein offensichtlicher Speicherüberlauf.

Eine weitere Herausforderung ergibt sich, wenn Sie an stringeine andere Funktion übergeben, damit die andere Funktion in das Array schreiben kann. Sie müssen der anderen Funktion mitteilen, wie lang das Array ist, damit es nicht zu einem Überlauf kommt. Sie könnten stringdas Ergebnis von weitergeben, strlen()aber der Thread erklärt, wie dies explodieren kann, wenn stringes nicht nullterminiert ist.

Sie sollten einen String mit einer festen Größe (vorzugsweise als Konstante definiert) zuweisen und dann das Array und die feste Größe an die andere Funktion übergeben. @ John Bodes Kommentar (e) sind korrekt, und es gibt Möglichkeiten, diese Risiken zu mindern. Sie erfordern auch mehr Aufwand von Ihrer Seite, um sie zu verwenden.

Nach meiner Erfahrung ist der Wert, den ich für initialisiert habe char[], normalerweise zu klein für die anderen Werte, die ich dort einfügen muss. Die Verwendung einer definierten Konstante hilft, dieses Problem zu vermeiden.


sizeof stringgibt Ihnen die Größe des Puffers (8 Bytes); Verwenden Sie das Ergebnis dieses Ausdrucks, anstatt sich strlenGedanken über das Gedächtnis zu machen.
Ebenso können Sie einen Scheck, bevor der Anruf an , strcpyum zu sehen , ob Ihre Zielpuffer groß genug für die Quellzeichenfolge ist: if (sizeof target > strlen(src)) { strcpy (target, src); }.
Ja, wenn Sie das Array an eine Funktion haben, müssen Sie auch seine physische Größe weitergeben müssen: foo (array, sizeof array / sizeof *array);. - John Bode

Gemeinschaft
quelle
2
sizeof stringgibt Ihnen die Größe des Puffers (8 Bytes); Verwenden Sie das Ergebnis dieses Ausdrucks, anstatt sich strlenGedanken über das Gedächtnis zu machen. Ebenso können Sie einen Scheck, bevor der Anruf an , strcpyum zu sehen , ob Ihre Zielpuffer ausreichend groß für die Quellzeichenfolge ist: if (sizeof target > strlen(src)) { strcpy (target, src); }. Ja, wenn Sie das Array an eine Funktion haben, müssen Sie auch seine physische Größe weitergeben müssen: foo (array, sizeof array / sizeof *array);.
John Bode
1
@ JohnBode - danke, und das sind gute Punkte. Ich habe Ihren Kommentar in meine Antwort aufgenommen.
1
Genauer gesagt, führen die meisten Verweise auf den Arraynamen stringzu einer impliziten Konvertierung in char*, die auf das erste Element des Arrays verweist. Dadurch gehen die Informationen zu den Array-Grenzen verloren. Ein Funktionsaufruf ist nur einer von vielen Kontexten, in denen dies geschieht. char *ptr = string;ist ein anderer. Auch string[0]ist ein Beispiel dafür; Der []Operator arbeitet mit Zeigern, nicht direkt mit Arrays. Empfohlene Lektüre: § 6 des comp.lang.c FAQ .
Keith Thompson
Endlich eine Antwort, die sich tatsächlich auf die Frage bezieht!
mastov
2

Ich denke, die Idee der "schlechten Praxis" kommt von der Tatsache, dass diese Form:

char string[] = "october is a nice month";

macht implizit einen strcpy vom Quellmaschinencode zum Stack.

Es ist effizienter, nur eine Verknüpfung zu dieser Zeichenfolge zu verarbeiten. Wie mit :

char *string = "october is a nice month";

oder direkt:

strcpy(output, "october is a nice month");

(aber in den meisten Codes spielt es wahrscheinlich keine Rolle)

toto
quelle
Würde es nicht nur eine Kopie erstellen, wenn Sie versuchen, sie zu ändern? Ich würde denken, der Compiler wäre schlauer als das
Cole Johnson
1
Was ist mit Fällen wie char time_buf[] = "00:00";dem Ändern eines Puffers? A char *zu einem Stringliteral initialisiert wird auf die Adresse des ersten Bytes, so versuchen , es führt zu undefiniertem Verhalten zu ändern , da die Methode der Speicher des Stringliteral ist unbekannt (Implementierung definiert), während das Bytes einer Modifizierung char[]ist völlig legal , weil Die Initialisierung kopiert die Bytes in einen beschreibbaren Speicherplatz auf dem Stapel. Zu sagen, dass es "weniger effizient" oder "schlechte Praxis" ist, ohne auf die Nuancen von einzugehen, char* vs char[]ist irreführend.
Braden Best
-3

Nie ist wirklich viel Zeit, aber Sie sollten die Initialisierung von char [] to string vermeiden, da "string" const char * ist und Sie es char * zuweisen. Wenn Sie also dieses Zeichen [] an die Methode übergeben, die Daten ändert, kann dies zu einem interessanten Verhalten führen.

Wie gesagt, ich habe ein bisschen char [] mit char * gemischt, das ist nicht gut, da sie sich ein bisschen unterscheiden.

Es ist nichts Falsches daran, einem char-Array Daten zuzuweisen, aber da dieses Array als 'String' (char *) verwendet werden soll, kann leicht vergessen werden, dass Sie dieses Array nicht ändern sollten.

Dainius
quelle
3
Falsch. Die Initialisierung kopiert den Inhalt des String-Literal in das Array. Das Array-Objekt ist es nicht, es constsei denn, Sie definieren es auf diese Weise. (Und String-Literale in C sind es nicht const, obwohl jeder Versuch, ein String-Literal zu ändern, ein undefiniertes Verhalten hat.) char *s = "literal";Hat das Verhalten, von dem Sie sprechen. Es ist besser geschrieben alsconst char *s = "literal";
Keith Thompson
in der Tat meine Schuld, ich mischte char [] mit char *. Aber ich wäre mir nicht so sicher, ob ich Inhalte in ein Array kopieren soll. Schnelle Überprüfung mit MS C Compilator zeigt, dass 'char c [] = "asdf";' erstellt 'string' im const-Segment und weist diese Adresse dann der Array-Variablen zu. Das ist eigentlich ein Grund, warum ich über die Vermeidung von Zuweisungen zu Nicht-Konstanten-Char-Arrays gesprochen habe.
Dainius
Ich bin skeptisch. Probieren Sie dieses Programm aus und teilen Sie mir mit, welche Ausgabe Sie erhalten.
Keith Thompson
2
"Und im Allgemeinen ist" asdf "eine Konstante, daher sollte sie als const deklariert werden." - Dieselbe Überlegung würde ein Ein consterfordern int n = 42;, da 42es sich um eine Konstante handelt.
Keith Thompson
1
Es ist egal, auf welcher Maschine Sie sich befinden. Der Sprachstandard garantiert, dass cänderbar ist. Es ist genau so eine Garantie wie die, die 1 + 1bewertet wird 2. Wenn das Programm, mit dem ich oben verlinkt habe, etwas anderes als Drucken ausführt EFGH, weist dies auf eine nicht konforme C-Implementierung hin.
Keith Thompson