Zurückgeben eines C-Strings aus einer Funktion

Lesezeit: 11 Minuten

Benutzeravatar von itsaboutcode
seinaboutcode

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.

  • 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

Benutzeravatar von cmroanirgo
cmroanirgo

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.

  1. 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
  2. Ü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::stringwenn 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).

  • Eigentlich muss die Funktion a zurückgeben char *da Zeichenfolgenliterale in C vom Typ sind char[]. Sie dürfen jedoch in keiner Weise modifiziert werden, also zurücksenden const char* bevorzugt (vgl securecoding.cert.org/confluence/x/mwAV). Rückkehr char * wird möglicherweise benötigt, wenn die Zeichenfolge in einer Legacy- oder externen Bibliotheksfunktion verwendet wird, die (leider) eine erwartet char* als Argument, auch wenn es nur daraus gelesen wird. C++ hingegen hat Zeichenfolgenliterale von const char[] type (und seit C++11 können Sie auch std::string Literale).

    – TManhente

    30. März 2014 um 12:33 Uhr


  • @cmroanirgo die mein Präfix erklärt dem Leser, dass die Funktion vom Benutzer erstellt wurde. Ich finde es durchaus sinnvoll, in einem solchen Kontext zu verwenden.

    – Menge

    30. Juli 2014 um 0:10 Uhr

  • Laut hier: stackoverflow.com/questions/9970295/… können Sie String-Literal zurückgeben

    – Giorgi Moniava

    22. Oktober 2015 um 22:19 Uhr

  • Der markierte Code fraught with problems im Abschnitt “Langlebigkeit der Daten” ist eigentlich vollkommen gültig. Zeichenfolgenliterale haben in C/C++ eine statische Lebensdauer. Siehe den Link, den Giorgi oben erwähnt.

    – ST0

    27. Oktober 2015 um 15:34 Uhr

  • @cmroanirgo Das Zurückgeben von Zeichenfolgenliteralen ist eine gute Übung und ein guter Stil. Es ist nicht “voller Probleme”, und es wird nicht 9 von 10 Mal abstürzen: Es wird niemals abstürzen. Sogar Compiler aus den 80er Jahren (zumindest die, die ich verwendet habe) unterstützen die unbegrenzte Lebensdauer von Zeichenfolgenliteralen korrekt. Hinweis: Ich bin mir nicht sicher, was Sie mit dem Bearbeiten der Antwort gemeint haben: Ich sehe immer noch, dass es anfällig für Abstürze ist.

    – zess

    6. Mai 2017 um 12:12 Uhr


AC-String ist als Zeiger auf ein Array von Zeichen definiert.

Wenn Sie keine Zeiger haben können, können Sie per Definition keine Zeichenfolgen haben.

  • Sie können ein Array an eine Funktion übergeben und dann mit diesem Array arbeiten: void foo( char array[], int length). Na sicher, array ist ein Zeiger unter der Haube, aber es ist nicht “explizit” ein Zeiger, und es kann daher für jemanden, der Arrays lernt, intuitiver sein, aber wer hat nicht ganz erlernte Zeiger.

    – jvriesem

    24. Februar 2020 um 22:49 Uhr

Benutzeravatar von elcuco
elcuco

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.

  • Dies ist im Allgemeinen ein schlechter Weg, um Dinge anzugehen. Das Zeichen* kann durch den umgebenden Code manipuliert werden. Das heißt, Sie können Dinge wie diese tun: strcpy(myFunction(), “Ein wirklich langer String”); und Ihr Programm stürzt aufgrund einer Zugriffsverletzung ab.

    – cmroanirgo

    18. Juni 2014 um 9:44 Uhr

  • Etwas fehlt in der Nähe “derjenige, der der Benutzer”.

    – Peter Mortensen

    30. April 2020 um 14:19 Uhr

Benutzeravatar von caf
Café

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.

Basierend auf Ihrer neu hinzugefügten Hintergrundgeschichte mit der Frage, warum nicht einfach eine Ganzzahl von 1 bis 12 für den Monat zurückgeben und die Funktion main() eine switch-Anweisung oder eine if-else-Leiter verwenden lassen, um zu entscheiden, was gedruckt werden soll? Es ist sicherlich nicht der beste Weg – char* wäre -, aber im Kontext eines Kurses wie diesem stelle ich mir vor, dass es wahrscheinlich der eleganteste ist.

Benutzeravatar von Peter Mortensen
Peter Mortensen

Der Rückgabetyp Ihrer Funktion ist ein einzelnes Zeichen (char). Sie sollten einen Zeiger auf das erste Element des Zeichenarrays zurückgeben. Wenn Sie keine Zeiger verwenden können, sind Sie aufgeschmissen. 🙁

Benutzeravatar von Neuron
Neuron

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.

1423390cookie-checkZurückgeben eines C-Strings aus einer Funktion

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy