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

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

Ist das wahr oder ist das nur seine (wenn auch ein“ 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 ja sein?

das ist Die Ausgabegröße

the size of september is 8 and the length is 9

sollte mit Sicherheit 10 betragen. Dies entspricht der Berechnung der Zeichenfolgengröße, bevor sie durch strcpy geändert wird, jedoch durch die Länge nachher.

Stimmt etwas mit meiner Syntax nicht oder was?


Hier ist die Antwort :

Es ist sowieso eine schlechte Praxis, ein char-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"); 

Kommentare

  • 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 “ eine schlechte Praxis „, da ein Literal const sein sollte und jeder neuere c ++ – Compiler eine Warnung (oder einen Fehler) ausgibt. Informationen zum Zuweisen eines const-Literals zu einem Nicht-const-Array.
  • @Andr é C ++ definiert Zeichenfolgenliterale als const-Arrays, da dies die einzig sichere Methode ist mit ihnen. Dass C nicht ‚ t ist, ist das Problem, also haben Sie eine soziale Regel, die die sichere Sache erzwingt
  • @Caleth. Ich weiß, ich habe eher versucht zu argumentieren, dass der Autor der Antwort sich der “ schlechten Praxis “ aus einer c ++ – Perspektive nähert.
  • @Andr é es ist keine ‚ schlechte Praxis in C ++, weil es nicht ‚ um zu üben, ‚ ist ein direkter Typfehler. Es sollte ein Typfehler in C sein, aber es ist nicht ‚ t, daher müssen Sie eine Style-Guide-Regel haben, die Ihnen Es ‚ ist verboten “

Antwort

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

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

In C (und Sie haben dies als C markiert), dass “ Dies ist so ziemlich die einzige Möglichkeit, ein Array von char mit einem Zeichenfolgenwert zu initialisieren (Initialisierung unterscheidet sich von Zuweisung). 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 von char gespeichert mit einem abschließenden 0-Byte beträgt die Größe des Arrays also 8 („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_LENGTH, was auch immer das sein mag).

Was Sie nicht tun können, ist etwas wie

char string[]; string = "october"; 

oder

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

usw. Im ersten Fall ist die Deklaration von string unvollständig , da keine Arraygröße angegeben wurde und es keinen Initialisierer gibt, von dem die Größe übernommen werden kann. In beiden Fällen In einigen Fällen funktioniert = nicht, da a) ein Array-Ausdruck wie string möglicherweise nicht das Ziel einer Zuweisung ist und b) Der Operator = ist ohnehin nicht definiert, um den Inhalt eines Arrays in ein anderes zu kopieren.

Mit demselben Token können Sie nicht

schreiben

char string[] = foo; 

wobei foo ein weiteres Array von char ist. Diese Form der Initialisierung funktioniert nur mit Zeichenfolgenliteralen.

BEARBEITEN

Ich sollte dies ändern, um zu sagen, dass Sie auch initialisieren können Arrays zum Halten eines Strings mit einem Initialisierer im Array-Stil, wie z. B.

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 Wenn Sie den Inhalt eines Arrays außerhalb einer Deklaration zuweisen, müssen Sie entweder strcpy/strncpy (für Zeichenfolgen mit 0-Terminierung) oder (für jeden anderen Array-Typ):

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! 

Kommentare

  • strncpy ist selten die richtige Antwort
  • @KeithThompson: Nicht anderer Meinung, nur der Vollständigkeit halber ‚ hinzugefügt.
  • Bitte beachten Sie, dass char[8] str = "october"; ist eine schlechte Praxis. Ich musste mich buchstäblich selbst zählen, um sicherzustellen, dass es kein ‚ kein Überlauf war und es bei Wartung bricht … z. Das Korrigieren eines Rechtschreibfehlers von seprate auf separate wird unterbrochen, wenn die Größe nicht aktualisiert wird.
  • Ich stimme djechlin zu ist aus den angegebenen Gründen eine schlechte Praxis. Die Antwort von JohnBode ‚ kommentiert ‚ die “ schlechte Praxis “ Aspekt (was der Hauptteil der Frage ist !!), erklärt nur, was Sie tun können oder nicht, um das Array zu initialisieren.
  • Minor: As ‚ Länge “ Der von strlen() zurückgegebene Wert enthält kein Nullzeichen unter Verwendung von MAX_MONTH_LENGTH für die maximale Größe, die für char string[] erforderlich ist.

sieht häufig falsch aus. IMO,MAX_MONTH_SIZEwäre hier besser.

Antwort

Das einzige Problem, an das ich mich erinnere, ist das Zuweisen eines 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 auf meiner Plattform (Linux) stürzt ab, wenn versucht wird, auf eine als schreibgeschützt gekennzeichnete Seite zu schreiben. Auf anderen Plattformen wird möglicherweise „September“ usw. gedruckt.

Das heißt, durch die Initialisierung per Literal wird der spezifische Reservierungsbetrag festgelegt, sodass dies nicht funktioniert:

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

Aber dies wird

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

Als letzte Bemerkung – ich würde strcpy überhaupt:

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 strncpy viel 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"; 

Kommentare

  • Es gibt ‚ noch a Risiko eines Pufferüberlaufs auf diesem strncpy, da ‚ die kopierte Zeichenfolge nicht null beendet, wenn die Länge von something_else ist größer als sizeof(buf). Normalerweise setze ich das letzte Zeichen buf[sizeof(buf)-1] = 0, um mich davor zu schützen, oder wenn buf mit Null initialisiert ist, verwende als Kopierlänge.
  • Verwenden Sie strlcpy oder strcpy_s oder sogar snprintf wenn nötig.
  • Behoben. Leider gibt es keine einfache tragbare Möglichkeit, dies zu tun, es sei denn, Sie haben den Luxus, mit den neuesten Compilern zu arbeiten (strlcpy und snprintf sind nicht direkt verfügbar Bei MSVC sind zumindest Bestellungen und strcpy_s nicht auf * nix).
  • @MaciejPiechotka: Gott sei Dank lehnte Unix den von Microsoft gesponserten Anhang k ab.

Antwort

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

Das Codebeispiel aus dem Link:

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

string wird auf dem Stapel mit einer Länge von 7 oder 8 Zeichen zugewiesen. Ich kann mich nicht erinnern, ob es auf diese Weise nullterminiert ist oder nicht – der Thread, mit dem Sie verknüpft haben, hat dies angegeben

Das Kopieren von „September“ über diese Zeichenfolge ist eine offensichtliche Speicherüberschreitung.

Eine weitere Herausforderung ergibt sich, wenn Sie string an eine andere Funktion übergebenso kann die andere Funktion in das Array schreiben. Sie müssen der anderen Funktion mitteilen, wie lang das Array ist, damit es keinen Überlauf erzeugt. Sie können string zusammen mit dem Ergebnis von strlen(), aber der Thread erklärt, wie dies explodieren kann, wenn string nicht nullterminiert ist.

Sie sind besser dran Zuweisen eines Strings mit einer festen Größe (vorzugsweise als Konstante definiert) und Übergeben des Arrays und der festen Größe an die andere Funktion. Die Kommentare von @John Bode sind korrekt, und es gibt Möglichkeiten, diese Risiken zu mindern. Sie erfordern auch mehr Aufwand von Ihrer Seite, um sie zu verwenden.

Meiner Erfahrung nach hat der Wert, den ich initialisiert habe, das char[] to ist 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 string gibt die Größe des Puffers an (8 Byte). Verwenden Sie das Ergebnis dieses Ausdrucks anstelle von strlen, wenn Sie sich Gedanken über den Speicher machen.
Ebenso können Sie vor dem Aufruf von strcpy um festzustellen, ob Ihr Zielpuffer groß genug für die Quellzeichenfolge ist: if (sizeof target > strlen(src)) { strcpy (target, src); }.
Ja, wenn Sie das Array an eine Funktion übergeben müssen, werden Sie „ll müssen auch ihre physische Größe übergeben: foo (array, sizeof array / sizeof *array);. – John Bode

Kommentare

  • sizeof string gibt Ihnen die Größe des Puffers (8 Bytes); Verwenden Sie das Ergebnis dieses Ausdrucks anstelle von strlen, wenn Sie ‚ über den Speicher besorgt sind. Ebenso können Sie vor dem Aufruf von strcpy überprüfen, ob Ihr Zielpuffer groß genug für die Quellzeichenfolge ist: if (sizeof target > strlen(src)) { strcpy (target, src); }. Ja, wenn Sie das Array an eine Funktion übergeben müssen, müssen Sie ‚ auch seine physische Größe übergeben: foo (array, sizeof array / sizeof *array);.
  • @JohnBode – danke, und das sind gute Punkte. Ich habe Ihren Kommentar in meine Antwort aufgenommen.
  • Genauer gesagt führen die meisten Verweise auf den Array-Namen string zu einer impliziten Konvertierung in zeigt auf das erste Element des Arrays. Dadurch gehen die Informationen zu den Arraygrenzen verloren. Ein Funktionsaufruf ist nur einer der vielen Kontexte, in denen dies geschieht. char *ptr = string; ist eine andere. Sogar string[0] ist ein Beispiel dafür; Der Operator [] arbeitet mit Zeigern, nicht direkt mit Arrays. Empfohlene Lektüre: Abschnitt 6 der FAQ zu comp.lang.c .
  • Endlich eine Antwort, die sich tatsächlich auf die Frage bezieht!

Antwort

Eine Sache, die keiner der Threads anspricht, ist folgende:

char whopping_great[8192] = "foo"; 

vs.

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

Ersteres führt ungefähr Folgendes aus:

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

Letzteres erledigt nur das Memcpy. Der C-Standard besteht darauf, dass alles initialisiert wird, wenn ein Teil eines Arrays initialisiert wird. In diesem Fall ist es also besser, es selbst zu tun. Ich denke, das war vielleicht das, worauf Treuss hinaus wollte.

Sicher

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

ist besser als entweder:

char whopping_big[8192] = {0}; 

oder

char whopping_big[8192] = ""; 

ps For Bonuspunkte können Sie tun:

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

, um eine Kompilierungszeit durch Null zu teilen, wenn Sie das Array überlaufen möchten.

Antwort

Ich denke, die Idee der „schlechten Praxis“ beruht auf der Tatsache, dass diese Form:

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

erstellt implizit einen Strcpy vom Quellcomputercode zum Stapel.

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

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

oder direkt:

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

(aber natürlich in den meisten Fällen Code, den es wahrscheinlich nicht spielt)

Kommentare

  • Würde ‚ nicht nur eine Kopie erstellen Wenn Sie versuchen, es zu ändern, würde ich denken, der Compiler wäre schlauer als das.
  • Was ist mit Fällen wie char time_buf[] = "00:00";, in denen Sie ‚ wird ein Puffer geändert? Ein char *, das mit einem Zeichenfolgenliteral initialisiert wurde, wird auf die Adresse des ersten Bytes gesetzt. Der Versuch, es zu ändern, führt daher zu undefiniertem Verhalten, weil Die Methode des Speichers des String-Literal ‚ ist unbekannt (Implementierung definiert), während das Ändern der Bytes eines char[] vollkommen legal ist, da das Die Initialisierung kopiert die Bytes in einen beschreibbaren Speicherplatz auf dem Stapel. Um zu sagen, dass ‚ “ weniger effizient ist oder “ schlechte Praxis “ ohne auf die Nuancen von char* vs char[] ist irreführend.

Antwort

Nie ist wirklich lange Zeit, aber Sie sollten Initialisierungszeichen vermeiden [] zu string, weil „string“ const char * ist und Sie es char * zuweisen. Wenn Sie dieses Zeichen [] an eine Methode übergeben, die Daten ändert, können Sie ein interessantes Verhalten haben.

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

Es ist nichts Falsches daran, Daten einem char-Array zuzuweisen. Da dieses Array jedoch als „Zeichenfolge“ (char *) verwendet werden soll, kann man leicht vergessen, dass Sie dies nicht ändern sollten Array.

Kommentare

  • Falsch. Die Initialisierung kopiert den Inhalt des Zeichenfolgenliteral in das Array. Das Array-Objekt ist nicht ‚ t const, es sei denn, Sie definieren es so.(Und String-Literale in C sind nicht const, obwohl jeder Versuch, ein String-Literal zu ändern, ein undefiniertes Verhalten hat.) char *s = "literal"; hat das Art von Verhalten, über das Sie ‚ sprechen; ‚ ist besser geschrieben als const char *s = "literal";
  • “ Und im Allgemeinen ist “ asdf “ eine Konstante, daher sollte sie als const deklariert werden. “ – Dieselbe Überlegung würde ein const für int n = 42; erfordern, da 42 ist eine Konstante.
  • ‚ spielt keine Rolle, auf welcher Maschine Sie ‚ sind. Der Sprachstandard garantiert, dass c geändert werden kann. ‚ ist genau so stark wie die Garantie, die 1 + 1 als 2 auswertet. Wenn das Programm, mit dem ich oben verlinkt habe , etwas anderes tut als EFGH, weist dies auf eine nicht konforme C-Implementierung hin.
  • @Dainus: Der MSVC-Compiler verfügt über eine Optimierung namens ‚ String-Pooling ‚, die eine einzelne Kopie von erstellt identische Zeichenfolgen in ein schreibgeschütztes Segment, wenn dies garantieren kann, dass ihre Verwendung schreibgeschützt ist. Deaktivieren Sie die Optimierung, um ‚ normales ‚ Verhalten anzuzeigen. Zu Ihrer Information: Für “ Bearbeiten und Fortfahren “ muss diese Option aktiviert sein. Weitere Informationen hier: msdn.microsoft.com/en-us/library/s0s0asdt.aspx
  • Ich denke, Dainius schlägt dies in vielen Fällen vor In einigen Fällen besteht der Fehler darin, dass die Variable selbst mit const char *const markiert werden sollte, um eine Änderung der Bytes oder des Zeigers selbst zu verhindern. In vielen Fällen lassen Programmierer jedoch eine oder beide veränderbar, sodass ein gewisser Laufzeitcode zulässig ist Ändern Sie eine scheinbar typisierte Konstante (die jedoch keine Konstante ist).

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.