Ich versuche, einen C-String von einer Funktion zurückzugeben, aber es funktioniert nicht. Hier ist mein Code.
char myFunction()
{
return "My String";
}
Im main
Ich nenne es so:
int main()
{
printf("%s", myFunction());
}
Ich habe auch einige andere Möglichkeiten für versucht myFunction
, aber sie funktionieren nicht. Zum Beispiel:
char myFunction()
{
char array[] = "my string";
return array;
}
Hinweis: Ich darf keine Pointer verwenden!
Kleiner Hintergrund zu diesem Problem:
Es gibt eine Funktion, die herausfindet, welcher Monat es ist. Wenn es beispielsweise 1 ist, wird Januar usw. zurückgegeben.
Wenn es also gedruckt werden soll, macht es das so: printf("Month: %s",calculateMonth(month));
. Jetzt besteht das Problem darin, wie man diese Zeichenfolge von zurückgibt calculateMonth
Funktion.
Ihre Funktionssignatur muss sein:
const char * myFunction()
{
return "my String";
}
Hintergrund:
Es ist so grundlegend für C & C++, aber es sollte wenig mehr Diskussion angebracht sein.
In C (& C++ für diese Angelegenheit) ist ein String nur ein Array von Bytes, das mit einem Null-Byte abgeschlossen wird – daher wird der Begriff “String-Null” verwendet, um diese besondere Art von String darzustellen. Es gibt andere Arten von Strings, aber in C (& C++) wird diese Variante von Natur aus von der Sprache selbst verstanden. Andere Sprachen (Java, Pascal usw.) verwenden andere Methoden zum Verständnis "my string"
.
Wenn Sie jemals die Windows-API verwenden (die sich in C++ befindet), sehen Sie ziemlich regelmäßig Funktionsparameter wie: “LPCSTR lpszName”. Der ‘sz’-Teil repräsentiert diese Vorstellung von ‘string-zero’: ein Array von Bytes mit einem Null-Terminator (/zero).
Klärung:
Für dieses „Intro“ verwende ich die Wörter „Bytes“ und „Zeichen“ austauschbar, weil es auf diese Weise einfacher zu lernen ist. Beachten Sie, dass es andere Methoden gibt (Wide-Characters und Multi-Byte-Character-Systeme (mbcs)), die verwendet werden, um mit internationalen Zeichen umzugehen. UTF-8 ist ein Beispiel für ein mbcs. Der Einleitung halber überspringe ich das alles ruhig.
Erinnerung:
Dies bedeutet, dass eine Zeichenfolge wie "my string"
verwendet tatsächlich 9+1 (=10!) Bytes. Dies ist wichtig zu wissen, wenn Sie endlich dazu kommen, Zeichenfolgen dynamisch zuzuweisen.
Ohne diese „abschließende Null“ haben Sie also keinen String. Sie haben ein Array von Zeichen (auch Puffer genannt) im Speicher.
Langlebigkeit der Daten:
Die Verwendung der Funktion auf diese Weise:
const char * myFunction()
{
return "my String";
}
int main()
{
const char* szSomeString = myFunction(); // Fraught with problems
printf("%s", szSomeString);
}
… wird Sie im Allgemeinen mit zufälligen unbehandelten Ausnahmen / Segmentfehlern und dergleichen landen, insbesondere “auf der Straße”.
Kurz gesagt, obwohl meine Antwort richtig ist – in 9 von 10 Fällen stürzt das Programm ab, wenn Sie es auf diese Weise verwenden, insbesondere wenn Sie der Meinung sind, dass es eine “gute Praxis” ist, dies auf diese Weise zu tun. Kurz gesagt: Es ist im Allgemeinen nicht.
Stellen Sie sich zum Beispiel vor, dass die Zeichenfolge irgendwann in der Zukunft auf irgendeine Weise manipuliert werden muss. Im Allgemeinen wird ein Programmierer „den einfachen Weg gehen“ und Code wie diesen schreiben (versuchen):
const char * myFunction(const char* name)
{
char szBuffer[255];
snprintf(szBuffer, sizeof(szBuffer), "Hi %s", name);
return szBuffer;
}
Das heißt, Ihr Programm stürzt ab, weil der Compiler (möglicherweise nicht) den von verwendeten Speicher freigegeben hat szBuffer
bis die printf()
in main()
wird genannt. (Ihr Compiler sollte Sie auch vorher vor solchen Problemen warnen.)
Es gibt zwei Möglichkeiten, Saiten zurückzugeben, die nicht so leicht kotzen.
- Zurückgeben von Puffern (statisch oder dynamisch zugewiesen), die eine Weile leben. Verwenden Sie in C++ ‘Hilfsklassen’ (z. B.
std::string
), um die Langlebigkeit von Daten zu handhaben (was eine Änderung des Rückgabewerts der Funktion erfordert), oder
- Übergeben Sie einen Puffer an die Funktion, der mit Informationen gefüllt wird.
Beachten Sie, dass es unmöglich ist, Strings ohne Zeiger in C zu verwenden. Wie ich gezeigt habe, sind sie synonym. Selbst in C++ mit Template-Klassen werden im Hintergrund immer Puffer (d. h. Zeiger) verwendet.
Also, um die (jetzt modifizierte Frage) besser zu beantworten. (Es gibt sicherlich eine Vielzahl von “anderen Antworten”, die bereitgestellt werden können.)
Sicherere Antworten:
Beispiel 1 mit statisch zugewiesenen Zeichenfolgen:
const char* calculateMonth(int month)
{
static char* months[] = {"Jan", "Feb", "Mar" .... };
static char badFood[] = "Unknown";
if (month < 1 || month > 12)
return badFood; // Choose whatever is appropriate for bad input. Crashing is never appropriate however.
else
return months[month-1];
}
int main()
{
printf("%s", calculateMonth(2)); // Prints "Feb"
}
Was zum static
Hier (viele Programmierer mögen diese Art der ‘Zuweisung’ nicht) ist, dass die Strings in das Datensegment des Programms eingefügt werden. Das heißt, es ist dauerhaft zugewiesen.
Wenn Sie zu C++ wechseln, verwenden Sie ähnliche Strategien:
class Foo
{
char _someData[12];
public:
const char* someFunction() const
{ // The final 'const' is to let the compiler know that nothing is changed in the class when this function is called.
return _someData;
}
}
… aber es ist wahrscheinlich einfacher, Hilfsklassen zu verwenden, wie z std::string
wenn Sie den Code für Ihren eigenen Gebrauch schreiben (und nicht Teil einer Bibliothek, die mit anderen geteilt werden soll).
Beispiel 2 mit Aufrufer-definierten Puffern:
Dies ist die “idiotensicherere” Art, Strings herumzureichen. Die zurückgegebenen Daten unterliegen keiner Manipulation durch den Anrufer. Das heißt, Beispiel 1 kann leicht von einem Anrufer missbraucht werden und Sie Anwendungsfehlern aussetzen. Auf diese Weise ist es viel sicherer (obwohl mehr Codezeilen verwendet werden):
void calculateMonth(int month, char* pszMonth, int buffersize)
{
const char* months[] = {"Jan", "Feb", "Mar" .... }; // Allocated dynamically during the function call. (Can be inefficient with a bad compiler)
if (!pszMonth || buffersize<1)
return; // Bad input. Let junk deal with junk data.
if (month<1 || month>12)
{
*pszMonth="\0"; // Return an 'empty' string
// OR: strncpy(pszMonth, "Bad Month", buffersize-1);
}
else
{
strncpy(pszMonth, months[month-1], buffersize-1);
}
pszMonth[buffersize-1] = '\0'; // Ensure a valid terminating zero! Many people forget this!
}
int main()
{
char month[16]; // 16 bytes allocated here on the stack.
calculateMonth(3, month, sizeof(month));
printf("%s", month); // Prints "Mar"
}
Es gibt viele Gründe, warum die zweite Methode besser ist, insbesondere wenn Sie eine Bibliothek schreiben, die von anderen verwendet werden soll (Sie müssen sich nicht auf ein bestimmtes Zuweisungs-/Aufhebungsschema festlegen, Dritte können Ihren Code nicht knacken, und Sie müssen nicht auf eine bestimmte Speicherverwaltungsbibliothek verlinken), aber wie bei jedem Code liegt es an Ihnen, was Ihnen am besten gefällt. Aus diesem Grund entscheiden sich die meisten Leute für Beispiel 1, bis sie so oft verbrannt wurden, dass sie sich weigern, es so zu schreiben 😉
Haftungsausschluss:
Ich bin vor einigen Jahren in den Ruhestand getreten und mein C ist jetzt ein bisschen eingerostet. Dieser Democode sollte alle ordnungsgemäß mit C kompiliert werden (es ist jedoch für jeden C++-Compiler in Ordnung).
Beachten Sie diese neue Funktion:
const char* myFunction()
{
static char array[] = "my string";
return array;
}
Ich habe “Array” als statisch definiert. Andernfalls verlässt die Variable (und der Zeiger, den Sie zurückgeben) den Gültigkeitsbereich, wenn die Funktion endet. Da dieser Speicher auf dem Stapel zugewiesen wird, und es Wille korrumpiert werden. Der Nachteil dieser Implementierung ist, dass der Code nicht wiedereintrittsfähig und nicht threadsicher ist.
Eine andere Alternative wäre die Verwendung malloc um die Zeichenfolge im Heap zuzuweisen und dann an den richtigen Stellen Ihres Codes freizugeben. Dieser Code ist reentrant und threadsicher.
Wie im Kommentar erwähnt, ist dies eine sehr schlechte Praxis, da ein Angreifer dann Code in Ihre Anwendung einfügen kann (er/sie muss den Code mit GDB öffnen, dann einen Haltepunkt setzen und den Wert einer zurückgegebenen Variablen so ändern, dass er überläuft und Spaß fängt gerade erst an).
Es wird viel eher empfohlen, den Aufrufer über Speicherzuweisungen zu informieren. Siehe dieses neue Beispiel:
char* myFunction(char* output_str, size_t max_len)
{
const char *str = "my string";
size_t l = strlen(str);
if (l+1 > max_len) {
return NULL;
}
strcpy(str, str, l);
return input;
}
Beachten Sie, dass der einzige Inhalt, der geändert werden kann, der des Benutzers ist. Ein weiterer Nebeneffekt – dieser Code ist jetzt Thread-sicher, zumindest aus Sicht der Bibliothek. Der Programmierer, der diese Methode aufruft, sollte überprüfen, ob der verwendete Speicherabschnitt threadsicher ist.
Ihr Problem liegt beim Rückgabetyp der Funktion – es muss sein:
char *myFunction()
…und dann wird Ihre ursprüngliche Formulierung funktionieren.
Beachten Sie, dass Sie kann nicht C-Strings haben, ohne dass Zeiger irgendwo entlang der Linie beteiligt sind.
Außerdem: Erhöhen Sie Ihre Compiler-Warnungen. Es hätte Sie davor warnen sollen, dass die Rückleitung a konvertiert char *
zu char
ohne explizite Besetzung.
Sie können das Array im Aufrufer erstellen, der die Hauptfunktion ist, und das Array an den Aufgerufenen übergeben, der Ihr ist myFunction()
. Daher myFunction
kann den String in das Array füllen. Sie müssen jedoch deklarieren myFunction()
wie
char* myFunction(char * buf, int buf_len){
strncpy(buf, "my string", buf_len);
return buf;
}
Und in main
, myFunction
soll so heißen:
char array[51];
memset(array, 0, 51); /* All bytes are set to '\0' */
printf("%s", myFunction(array, 50)); /* The buf_len argument is 50, not 51. This is to make sure the string in buf is always null-terminated (array[50] is always '\0') */
Es wird jedoch immer noch ein Zeiger verwendet.
Du leider brauchen Hinweise in diesem Fall.
– Nick Bedford
30. September 2009 um 6:01 Uhr
@Hayato Nun, ich glaube, wir sind hier Erwachsene und wissen, dass es 0 zurückgeben sollte, es war nur, um Beispiel lox zu geben.
– sein Aboutcode
30. September 2009 um 6:08 Uhr
return 0
ist standardmäßig nur in C99 (und C++) impliziert, aber nicht in C90.– hrnt
30. September 2009 um 6:08 Uhr
Dann werden Sie es nicht tun können, abgesehen von dummen Hacks, die sowieso nur eine wirklich kaputte Zeigermanipulation sind. Zeiger existieren aus einem bestimmten Grund… 😐
– GManNickG
30. September 2009 um 6:29 Uhr
Werfen Sie einen Blick auf die lange Erklärung von Steve Summit über Return-Arrays in C
– lifeisfoo
27. Oktober 2017 um 16:09 Uhr