Mein Programm ist gut genug für meine Aufgabe, aber ich weiß, dass es nicht gut ist
Lesezeit: 8 Minuten
Jo
Ich beginne gerade eine Hausarbeit für die Uni und es hat sich eine Frage für mich ergeben.
Ich verstehe nicht, wie man eine Zeichenfolge von einer Funktion zurückgibt, ohne ein Speicherleck zu haben.
char* trim(char* line) {
int start = 0;
int end = strlen(line) - 1;
/* find the start position of the string */
while(isspace(line[start]) != 0) {
start++;
}
//printf("start is %d\n", start);
/* find the position end of the string */
while(isspace(line[end]) != 0) {
end--;
}
//printf("end is %d\n", end);
/* calculate string length and add 1 for the sentinel */
int len = end - start + 2;
/* initialise char array to len and read in characters */
int i;
char* trimmed = calloc(sizeof(char), len);
for(i = 0; i < (len - 1); i++) {
trimmed[i] = line[start + i];
}
trimmed[len - 1] = '\0';
return trimmed;
}
Wie Sie sehen können, gebe ich einen Zeiger auf char zurück, das ein Array ist. Ich fand das, wenn ich versuchte, das ‘getrimmte’ Array zu erstellen, indem ich so etwas wie folgt machte:
char trimmed[len];
dann würde der Compiler eine Meldung ausgeben, die besagt, dass in dieser Zeile eine Konstante erwartet wurde. Ich gehe davon aus, dass Sie aus irgendeinem Grund beim Initialisieren eines Arrays keine Variablen als Array-Länge verwenden können, obwohl mir etwas sagt, dass das nicht stimmen kann.
Also habe ich stattdessen mein Array erstellt, indem ich einem char-Zeiger etwas Speicher zugewiesen habe.
Ich verstehe, dass diese Funktion für das, was sie zu tun versucht, wahrscheinlich nicht optimal ist, aber was ich wirklich wissen möchte, ist:
Können Sie normalerweise ein Array mit einer Variablen initialisieren, um die Länge wie folgt zu deklarieren:
Zeichen getrimmt[len];
Wenn ich ein Array dieses Typs hätte (char trimmte[]) hätte es denselben Rückgabetyp wie ein Zeiger auf char (dh char*).
Wenn ich mein Array erstelle, indem ich etwas Speicher zuweise und ihn einem Zeichenzeiger zuweise, wie gebe ich diesen Speicher frei? Es scheint mir, dass ich, nachdem ich dieses Array zurückgegeben habe, nicht darauf zugreifen kann, um es freizugeben, da es sich um eine lokale Variable handelt.
“Ich nehme an, das bedeutete, dass Sie aus irgendeinem Grund keine Variablen als Array-Länge verwenden können, wenn Sie ein Array initialisieren, obwohl mir etwas sagt, dass das nicht stimmen kann.” Nein, das ist richtig, Array-Größen müssen Kompilierzeitkonstanten sein.
– dezent
26. März 2010 um 17:04 Uhr
+1 für das Nachverfolgen eines Themas, selbst nachdem Sie wussten, dass es „gut genug für [your] Abtretung”
– Knallt
26. März 2010 um 17:06 Uhr
Dies beantwortet Ihre Frage nicht speziell, aber eine andere Option für diesen Fall wäre, die Zeichenfolge an Ort und Stelle zu kürzen. Dies ist möglich, da ein Trimmvorgang niemals die Länge der Saite erhöht. Offensichtlich muss der Code, der Ihre Methode verwendet, geändert werden, um diese Änderung zu verarbeiten.
– Jeffrey L Whitledge
26. März 2010 um 17:13 Uhr
Wie Neil in seiner Antwort betont, ist der Kommentar von detly für modernes C falsch. Der C99-Standard erlaubt Variablen in Array-Deklarationen. So Es ist ein gültiger Code. Viele Compiler verwenden jedoch standardmäßig den C89-Modus, nur für den Fall, dass Ihr Code auf einem DEC VAX oder was auch immer kompiliert werden soll. Das ist wahrscheinlich der Grund, warum Sie eine Fehlermeldung erhalten haben.
– Chuck
26. März 2010 um 17:18 Uhr
Ich denke, dass Jeffreys Antwort am elegantesten ist. Dies würde dazu führen, dass der gesamte ursprüngliche Speicher für die vom String-Objekt gehaltene Zeichenfolge zugewiesen wird, aber es scheint, dass es bei normaler Verwendung unwahrscheinlich ist, dass viel Leerraum, dh nicht viel Verschwendung, vorhanden ist.
– Aaron H.
26. März 2010 um 17:39 Uhr
Justin Ether
An Adresse (3) – Sie können free die neu zugewiesene Zeichenfolge aus Ihrem Anrufcode, sobald Sie damit fertig sind:
Dies erlegt dem Anrufer jedoch die Last auf, daran zu denken, den Speicher freizugeben. Stattdessen könnten Sie erwägen, einen zugewiesenen Puffer und eine Puffergröße an zu übergeben trim()zum Beispiel:
void trim(char* line, char *trimmed_buf, int trimmed_buf_len){ ... }
Neil hat Ihre anderen Fragen hervorragend beantwortet. Im Grunde eine Array-Deklaration char trimmed[len]; deklariert eine lokale Variable auf der Stapelwährend es also syntaktisch korrekt ist, a zurückzugeben char * zu diesem Speicher, ist der Speicherort, auf den er zeigt, nicht mehr gültig.
Dies ist die beste Antwort, da es dem Aufrufer ermöglicht, ein Array zu verwenden, das auf dem Stapel oder auf einem beliebigen Heap seiner Wahl zugewiesen ist, anstatt ein bestimmtes Speicherverwaltungsmodell zu erzwingen. Die Hauptsache ist, sowohl den Startzeiger als auch die Länge zu übergeben, damit es nicht zu Überschreibungen kommt.
– Steven Sudit
26. März 2010 um 17:07 Uhr
Obwohl dies üblich ist, macht es keinen Sinn, danach zu suchen NULL vor dem Anruf free. Das free Funktion tut dies bereits NULL intern testen und den Test noch einmal hinzufügen, fügt keinen Wert hinzu (im Gegenteil, ich denke, es bietet einen negativen Wert, da es Rauschen ist).
– hlovdal
26. März 2010 um 17:18 Uhr
+1, danke für den Tipp! Ich habe es nur eingefügt, falls er versucht, etwas mit dem Zeiger zu tun, bevor er anruft freein diesem Fall a NULL prüfen wäre eine gute Idee.
– Justin Ethier
26. März 2010 um 17:25 Uhr
Danke Justin und an alle, die gepostet haben, es hat sehr geholfen. Ich sehe jetzt, dass ich immer noch auf den aufgerufenen Speicher außerhalb der Funktion zugreifen kann. Das habe ich nicht bekommen. Ich dachte, ich hätte den Kontakt für immer verloren. Aber was zurückgegeben wird, ist ein Zeiger auf die Adresse dieses Speichers, sodass ich ihn einfach verwenden kann, um ihn zurück zu verfolgen und ihn freizugeben. Kühl.
– Jo
26. März 2010 um 17:28 Uhr
NULL-Prüfungen nach der Speicherzuweisung sind erforderlich, aber ich empfehle dringend, den Test umzukehren zu if (tmp == NULL) {...} stattdessen um nicht den Normalfall zu testen, sondern die Ausnahmen zu testen (siehe stackoverflow.com/questions/114342/…)
– hlovdal
26. März 2010 um 17:33 Uhr
Um Ihre spezifischen Fragen zu beantworten, die Syntax:
char trimmed[len];
wo len eine Variable ist, ist nur in C99 erlaubt, nicht in C89 oder in C++. Der Rückgabetyp wäre tatsächlich a char *gibt aber die lokale Variable zurück trimmed würde zu undefiniertem Verhalten führen, also tun Sie es nicht. Und wenn Sie ein Array dynamisch in einer Funktion über zuweisen calloc und es zurückzugeben, ist es Sache des Aufrufers der Funktion, es freizugeben, indem er den Zeiger verwendet, den die Funktion zurückgibt.
hlovdal
In Bezug auf die dynamische Größenanpassung eine Array-Deklaration wie char trimmed[len]; die neueste Version des C-Standards (ISO/IEC 9899:1999) erlaubt dies, aber für diese Funktion würde es überhaupt nicht helfen. Das trimmed Variable hat ihren Geltungsbereich innerhalb der trim Funktion und wird auf dem Stapel zugewiesen. Also, wenn Sie setzen return trimmed; In Ihrem Code geben Sie einen Zeiger auf eine Variable auf dem Stapel zurück, und der Teil des Stapels, in dem sich diese Variable befindet, wird an dem Punkt freigegeben, an dem die Funktion zurückkehrt, sodass das nicht so gut funktioniert …
Lassen Sie den Aufrufer einen Zeiger auf einen Speicherbereich (eine maximale Länge) an die Funktion übergeben. Auf diese Weise ist der Aufrufer für die Zuweisung (und Freigabe) des Speichers verantwortlich, und die Funktion liefert nur eine Ausgabe, die auf den an die Funktion übergebenen Puffer beschränkt ist.
Sicher, die Person, die die Funktion verwendet, könnte die Dinge immer noch durcheinander bringen (indem sie eine Länge angibt, die nichts mit der tatsächlichen Puffergröße zu tun hat, aber dann können Sie richtig argumentieren, dass es der Fehler des Aufrufers ist, nicht dieser Funktionsfehler.
Abgesehen von C++-Lösungen wie using std::stringkönnten Sie immer ein Array mit einer festgelegten Größe zuweisen und die Größe als Parameter und das Array als Parameter per Referenz oder als Zeiger übergeben?
Auf diese Weise werden die Daten im gleichen Umfang zugewiesen und es gibt kein Speicherleck.
Andererseits können Sie den Speicher nach dem Anruf jederzeit freigeben. Dies bedeutet, dass der Code, der für die Erstellung verantwortlich ist, und der Code, der für die Zerstörung der Daten verantwortlich ist, nicht identisch sind, was zu Fehlern und Fehlern führen kann.
Tuomas Pelkonen
Zuordnung und Löschung außerhalb der Funktion:
Ordnen Sie es mit getrimmtem Zeichen vom Stapel zu[SIZE] und übergeben Sie es an die Funktion oder vom Heap mit calloc und übergeben Sie es an die Funktion.
Zuweisung innerhalb der Funktion und Löschung außerhalb:
Sie weisen es innerhalb der Funktion mit calloc zu, aber der Aufrufer muss es mit free freigeben.
Jeff Walker
Hm, also Antworten auf deine Fragen:
Ja, das Array würde auf dem Stack und nicht auf dem Heap zugewiesen und würde freigegeben, wenn die Funktion zurückkehrt.
ja, Char[] entspricht meistens char*. Es ist am besten, diese getrennt zu halten, da es einen semantischen Unterschied gibt.
Mit einem beliebigen Zeiger auf den Speicher können Sie kostenlos verwenden. In Ihrem Fall würden Sie die Rückgabefunktion freigeben. Das gilt als ziemlich schlechte Form und fehleranfällig. Sie möchten normalerweise, dass der Allocat’er der Free()er ist. Sie könnten vielleicht einen Puffer für den neuen Platz übergeben, oder der Aufrufer der Funktion könnte dem zustimmen char * line Inhalte, die über die bestehenden Inhalte geschrieben werden. Dies funktioniert, da trim() immer nur Sachen entfernen würde. Ich denke, der übergebene Puffer würde öfter funktionieren und ist eine gute Sache, sich daran zu gewöhnen.
Erwägen Sie für Ihre Funktion die Verwendung von memcpy() oder seinen Cousins, um Bytes in und aus einem Zeichenpuffer zu kopieren.
13703500cookie-checkMein Programm ist gut genug für meine Aufgabe, aber ich weiß, dass es nicht gut istyes
“Ich nehme an, das bedeutete, dass Sie aus irgendeinem Grund keine Variablen als Array-Länge verwenden können, wenn Sie ein Array initialisieren, obwohl mir etwas sagt, dass das nicht stimmen kann.” Nein, das ist richtig, Array-Größen müssen Kompilierzeitkonstanten sein.
– dezent
26. März 2010 um 17:04 Uhr
+1 für das Nachverfolgen eines Themas, selbst nachdem Sie wussten, dass es „gut genug für [your] Abtretung”
– Knallt
26. März 2010 um 17:06 Uhr
Dies beantwortet Ihre Frage nicht speziell, aber eine andere Option für diesen Fall wäre, die Zeichenfolge an Ort und Stelle zu kürzen. Dies ist möglich, da ein Trimmvorgang niemals die Länge der Saite erhöht. Offensichtlich muss der Code, der Ihre Methode verwendet, geändert werden, um diese Änderung zu verarbeiten.
– Jeffrey L Whitledge
26. März 2010 um 17:13 Uhr
Wie Neil in seiner Antwort betont, ist der Kommentar von detly für modernes C falsch. Der C99-Standard erlaubt Variablen in Array-Deklarationen. So Es ist ein gültiger Code. Viele Compiler verwenden jedoch standardmäßig den C89-Modus, nur für den Fall, dass Ihr Code auf einem DEC VAX oder was auch immer kompiliert werden soll. Das ist wahrscheinlich der Grund, warum Sie eine Fehlermeldung erhalten haben.
– Chuck
26. März 2010 um 17:18 Uhr
Ich denke, dass Jeffreys Antwort am elegantesten ist. Dies würde dazu führen, dass der gesamte ursprüngliche Speicher für die vom String-Objekt gehaltene Zeichenfolge zugewiesen wird, aber es scheint, dass es bei normaler Verwendung unwahrscheinlich ist, dass viel Leerraum, dh nicht viel Verschwendung, vorhanden ist.
– Aaron H.
26. März 2010 um 17:39 Uhr