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 vonseprate
aufseparate
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 vonMAX_MONTH_LENGTH
für die maximale Größe, die fürchar string[]
erforderlich ist.
sieht häufig falsch aus. IMO,MAX_MONTH_SIZE
wä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 vonsomething_else
ist größer alssizeof(buf)
. Normalerweise setze ich das letzte Zeichenbuf[sizeof(buf)-1] = 0
, um mich davor zu schützen, oder wennbuf
mit Null initialisiert ist, verwende als Kopierlänge. - Verwenden Sie
strlcpy
oderstrcpy_s
oder sogarsnprintf
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
undsnprintf
sind nicht direkt verfügbar Bei MSVC sind zumindest Bestellungen undstrcpy_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 vonstrlen
, wenn Sie ‚ über den Speicher besorgt sind. Ebenso können Sie vor dem Aufruf vonstrcpy
ü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. Sogarstring[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? Einchar *
, 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 eineschar[]
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 vonchar* 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 nichtconst
, 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 alsconst char *s = "literal";
- “ Und im Allgemeinen ist “ asdf “ eine Konstante, daher sollte sie als const deklariert werden. “ – Dieselbe Überlegung würde ein
const
fürint n = 42;
erfordern, da42
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, die1 + 1
als2
auswertet. Wenn das Programm, mit dem ich oben verlinkt habe , etwas anderes tut alsEFGH
, 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).