Rückgabe eines C-Strings von einer Funktion

109

Ich versuche, eine C-Zeichenfolge von einer Funktion zurückzugeben, aber es funktioniert nicht. Hier ist mein Code.

char myFunction()
{
    return "My String";
}

In mainnenne ich es so:

int main()
{
  printf("%s", myFunction());
}

Ich habe auch einige andere Möglichkeiten ausprobiert myFunction, aber sie funktionieren nicht. Beispielsweise:

char myFunction()
{
  char array[] = "my string";
  return array;
}

Hinweis: Ich darf keine Zeiger verwenden!

Wenig 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 gedruckt werden soll, geht es folgendermaßen vor : printf("Month: %s",calculateMonth(month));. Das Problem ist nun, wie diese Zeichenfolge von der calculateMonthFunktion zurückgegeben werden kann.

itsaboutcode
quelle
10
Leider benötigen Sie in diesem Fall Zeiger.
Nick Bedford
1
@ Hayato Nun, ich glaube, wir sind Erwachsene hier und wissen, dass es 0 zurückgeben sollte, es war nur um Beispiel lox zu geben ..
itsaboutcode
3
return 0wird standardmäßig nur in C99 (und C ++) impliziert, nicht jedoch in C90.
hrnt
1
Dann wirst du es nicht schaffen, neben dummen Hacks, die sowieso nur eine kaputte Zeigermanipulation sind. Zeiger existieren aus einem Grund ...: |
GManNickG
1
Werfen

Antworten:

222

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 (und auch in C ++) ist eine Zeichenfolge nur ein Array von Bytes, die mit einem Null-Byte abgeschlossen sind. Daher wird der Begriff "Zeichenfolge-Null" verwendet, um diese bestimmte Zeichenfolge darzustellen. Es gibt andere Arten von Zeichenfolgen, aber in C (& C ++) wird diese Variante von der Sprache selbst von Natur aus verstanden. Andere Sprachen (Java, Pascal usw.) verwenden andere Methoden, um "meine Zeichenfolge" zu verstehen.

Wenn Sie jemals die Windows-API (in C ++) verwenden, werden regelmäßig Funktionsparameter wie "LPCSTR lpszName" angezeigt. Der 'sz'-Teil repräsentiert diesen Begriff von' String-Null ': ein Array von Bytes mit einem Null-Terminator (/ Null).

Klärung:

Für dieses "Intro" verwende ich die Wörter "Bytes" und "Zeichen" synonym, da es einfacher ist, auf diese Weise zu lernen. Beachten Sie, dass es andere Methoden gibt ( Breitzeichen- und Multi-Byte-Zeichensysteme ( MBCs )), die zur Bewältigung internationaler Zeichen verwendet werden. UTF-8 ist ein Beispiel für ein mbcs. Um das Intro zu verbessern, überspringe ich das alles leise.

Erinnerung:

Dies bedeutet, dass eine Zeichenfolge wie "meine Zeichenfolge" tatsächlich 9 + 1 (= 10!) Bytes verwendet. Dies ist wichtig zu wissen, wenn Sie endlich dazu kommen, Zeichenfolgen dynamisch zuzuweisen.

Ohne diese 'Null beenden' haben Sie also keine Zeichenfolge. Sie haben eine Reihe von Zeichen (auch als Puffer bezeichnet) im Speicher.

Langlebigkeit der Daten:

Die Verwendung der Funktion folgendermaßen:

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 - 9 von 10 Fällen erhalten Sie ein Programm, das abstürzt, 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. Kurzum: Im Allgemeinen nicht.

Stellen Sie sich zum Beispiel irgendwann in der Zukunft vor, dass die Zeichenfolge jetzt auf irgendeine Weise manipuliert werden muss. Im Allgemeinen nimmt ein Codierer den einfachen Weg und schreibt Code wie folgt:

const char * myFunction(const char* name)
{
    char szBuffer[255];
    snprintf(szBuffer, sizeof(szBuffer), "Hi %s", name);
    return szBuffer;
}

Das heißt, wird Ihr Programm zum Absturz bringen , da der Compiler (kann / darf nicht) , um den von belegten Speicher freigegeben hat szBufferdurch die Zeit , die printf()in main()genannt wird. (Ihr Compiler sollte Sie auch vorher vor solchen Problemen warnen.)

Es gibt zwei Möglichkeiten, Zeichenfolgen zurückzugeben, die nicht so schnell gesperrt werden.

  1. Rückgabe von Puffern (statisch oder dynamisch zugewiesen), die eine Weile leben. Verwenden Sie in C ++ beispielsweise 'Hilfsklassen' std::string, um die Langlebigkeit von Daten zu bewältigen (für die der Rückgabewert der Funktion geändert werden muss), oder
  2. Übergeben Sie einen Puffer an die Funktion, die mit Informationen gefüllt wird.

Beachten Sie, dass es unmöglich ist, Zeichenfolgen ohne Verwendung von Zeigern in C zu verwenden. Wie ich gezeigt habe, sind sie synonym. Selbst in C ++ mit Vorlagenklassen werden immer Puffer (dh Zeiger) im Hintergrund verwendet.

Also, um die (jetzt modifizierte Frage) besser zu beantworten. (Es gibt sicher 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 die 'statische' hier tut (viele Programmierer mögen diese Art der 'Zuweisung' nicht), ist, dass die Zeichenfolgen 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, z. B. std::stringwenn Sie den Code für Ihren eigenen Gebrauch schreiben (und nicht Teil einer Bibliothek, die für andere freigegeben werden soll).

Beispiel 2 unter Verwendung von vom Anrufer definierten Puffern:

Dies ist die "narrensicherere" Art, Saiten herumzugeben. Die zurückgegebenen Daten unterliegen keiner Manipulation durch den Anrufer. Das heißt, Beispiel 1 kann leicht von einem anrufenden Teilnehmer 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 Zuordnungs- / Freigabeschema festlegen, Dritte können Ihren Code nicht beschädigen). und Sie müssen keine Verknüpfung zu einer bestimmten Speicherverwaltungsbibliothek herstellen), aber wie bei jedem Code liegt es an Ihnen, was Ihnen am besten gefällt. Aus diesem Grund entscheiden sich die meisten Menschen 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 gegangen und mein C ist jetzt etwas rostig. Dieser Demo-Code sollte alle ordnungsgemäß mit C kompiliert werden (dies ist jedoch für jeden C ++ - Compiler in Ordnung).

cmroanirgo
quelle
2
Tatsächlich muss die Funktion a zurückgeben char *, da Zeichenfolgenliterale in C vom Typ sind char[]. Sie dürfen jedoch in keiner Weise geändert werden, daher wird die Rückgabe const char*bevorzugt (siehe Securecoding.cert.org/confluence/x/mwAV ). Eine Rückgabe char *kann erforderlich sein, wenn die Zeichenfolge in einer Legacy- oder externen Bibliotheksfunktion verwendet wird, die (leider) ein As- char*Argument erwartet , auch wenn sie nur schwer zu lesen ist. C ++ hingegen verfügt über String-Literale vom const char[]Typ (und seit C ++ 11 können Sie auch std::stringLiterale verwenden).
TManhente
17
@cmroanirgo Das Präfix my erklärt dem Leser, dass die Funktion vom Benutzer erstellt wurde. Ich finde es durchaus vernünftig, in einem solchen Kontext zu verwenden.
Quant
4
Laut hier: stackoverflow.com/questions/9970295/… können Sie String-Literal
Giorgim
6
Der fraught with problemsim Abschnitt "Langlebigkeit der Daten" gekennzeichnete Code ist tatsächlich vollkommen gültig. String-Literale haben in C / C ++ statische Lebensdauern. Siehe den Link, den Giorgi oben erwähnt.
Chengiz
1
@cmroanirgo Das Zurückgeben von String-Literalen ist eine gute Übung und ein guter Stil. Es ist nicht "mit Problemen behaftet" und es wird nicht 9 von 10 Mal abstürzen: Es wird niemals abstürzen. Selbst Compiler aus den 80ern (zumindest die, die ich verwendet habe) unterstützen die unbegrenzte Lebensdauer von String-Literalen korrekt. Hinweis: Ich bin mir nicht sicher, was Sie mit dem Bearbeiten der Antwort gemeint haben: Ich sehe immer noch, dass es zu Abstürzen neigt.
beendet
12

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

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

Crashworks
quelle
Sie können ein Array an eine Funktion übergeben und dann dieses Array bearbeiten : void foo( char array[], int length). Natürlich arrayist es ein Zeiger unter der Haube, aber es ist nicht "explizit" ein Zeiger, und es kann daher für jemanden intuitiver sein, der Arrays lernt, aber nicht ganz Zeiger gelernt hat.
Jvriesem
12

Beachten Sie diese neue Funktion:

const char* myFunction()
{
    static char array[] = "my string";
    return array;
}

Ich habe "Array" als statisch definiert. Andernfalls wird beim Beenden der Funktion die Variable (und der von Ihnen zurückgegebene Zeiger) nicht mehr angezeigt. Da dieser Speicher auf dem Stapel zugewiesen ist, wird er beschädigt. Der Nachteil dieser Implementierung ist, dass der Code nicht wiedereintrittsfähig und nicht threadsicher ist.

Eine andere Alternative wäre, malloc zu verwenden, um die Zeichenfolge im Heap zuzuweisen und dann an den richtigen Stellen Ihres Codes freizugeben. Dieser Code ist wiedereintrittsfähig und threadsicher.

Wie im Kommentar erwähnt, ist dies eine sehr schlechte Vorgehensweise, da ein Angreifer dann Code in Ihre Anwendung einfügen kann (er muss den Code mit GDB öffnen, dann einen Haltepunkt erstellen und den Wert einer zurückgegebenen Variablen in Überlauf und ändern Spaß fängt gerade erst an).

Es wird viel mehr empfohlen, den Anrufer mit der Speicherzuweisung zu beauftragen. 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 threadsicher, zumindest aus Sicht der Bibliothek. Der Programmierer, der diese Methode aufruft, sollte überprüfen, ob der verwendete Speicherabschnitt threadsicher ist.

elcuco
quelle
2
Dies ist im Allgemeinen ein schlechter Weg, um Dinge zu erledigen. Das Zeichen * kann durch den umgebenden Code manipuliert werden. Das heißt, Sie können folgende Dinge tun: strcpy (myFunction (), "Eine wirklich lange Zeichenfolge"); und Ihr Programm stürzt aufgrund einer Zugriffsverletzung ab.
cmroanirgo
In der Nähe von "dem, den der Benutzer" fehlt etwas .
Peter Mortensen
8

Ihr Problem ist der Rückgabetyp der Funktion - es muss sein:

char *myFunction()

... und dann funktioniert Ihre ursprüngliche Formulierung.

Beachten Sie, dass Sie keine C-Zeichenfolgen haben können, ohne dass Zeiger irgendwo entlang der Linie beteiligt sind.

Außerdem: Erhöhen Sie die Compiler-Warnungen. Es sollte haben Sie über diese Rückleitung warnt eine Umwandlung char *zu , charohne eine explizite Umwandlung.

caf
quelle
1
Ich denke, die Signatur sollte const char * sein, da die Zeichenfolge ein Literal ist, aber wenn ich mich nicht irre, wird der Compiler dies akzeptieren.
Luke
5

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 main () - Funktion 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 einer solchen Klasse stelle ich mir vor, dass es wahrscheinlich der eleganteste ist.

Twisol
quelle
3

Sie können das Array im Aufrufer erstellen, der die Hauptfunktion darstellt, und das Array an den Angerufenen übergeben, der Ihre myFunction () ist. Somit kann myFunction den String in das Array füllen. Sie müssen jedoch myFunction () als deklarieren

char* myFunction(char * buf, int buf_len){
  strncpy(buf, "my string", buf_len);
  return buf;
}

Und in der Hauptfunktion sollte myFunction folgendermaßen aufgerufen werden:

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 weiterhin ein Zeiger verwendet.

ChainLooper
quelle
2

Ihr Funktionsrückgabetyp 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 geschraubt. :((

hrnt
quelle
2

Oder wie wäre es mit diesem:

void print_month(int month)
{
    switch (month)
    {
        case 0:
            printf("January");
            break;
        case 1:
            printf("february");
            break;
        ...etc...
    }
}

Und nennen Sie das mit dem Monat, den Sie woanders berechnen.

Sebastiaan M.
quelle
1
+1 nicht das, was OP gefragt hat, aber dies ist wahrscheinlich das, was die Zuweisung von Ihnen erwartet, da er keine Zeiger verwenden kann.
Vitim.us
Sogar printf verwendet Zeiger. Ein Zeiger ist wie ein Messer - wichtig zum Leben und Arbeiten, aber Sie müssen ihn am Griff halten und mit der scharfen Seite schneiden, sonst haben Sie eine schlechte Zeit. Die unglückliche Platzierung von Leerzeichen in der Funktionsdefinition ist für viele neue C-Programmierer ein Hirnfehler. char * func (char * s); char func (char * s); char func * char * s); sind alle gleich, sehen aber alle unterschiedlich aus, und um die Verwirrung zu verstärken, ist * auch der De-Referenz-Operator für Variablen, die Zeiger sind.
Chris Reid
1

A charist nur ein einzelnes Ein-Byte-Zeichen. Es kann weder die Zeichenfolge speichern, noch ist es ein Zeiger (den Sie anscheinend nicht haben können). Daher können Sie Ihr Problem nicht lösen, ohne Zeiger zu verwenden (was char[]syntaktischer Zucker ist).

Nick Bedford
quelle
1

Wenn Sie wirklich keine Zeiger verwenden können, gehen Sie wie folgt vor:

char get_string_char(int index)
{
    static char array[] = "my string";
    return array[index];
}

int main()
{
    for (int i = 0; i < 9; ++i)
        printf("%c", get_string_char(i));
    printf("\n");
    return 0;
}

Die magische Zahl 9 ist schrecklich, und dies ist kein Beispiel für eine gute Programmierung. Aber du verstehst, worum es geht. Beachten Sie, dass Zeiger und Arrays dasselbe sind (Art von), also ist dies ein bisschen Betrug.

Sebastiaan M.
quelle
Wenn Sie solche Lösungen für Hausaufgabenprobleme implementieren müssen, sind Ihre vorläufigen Annahmen normalerweise falsch.
hrnt
1

Nun, in Ihrem Code versuchen Sie, a zurückzugeben String(in C ist das nichts anderes als ein nullterminiertes Array von Zeichen), aber der Rückgabetyp Ihrer Funktion ist charder, der Ihnen alle Probleme bereitet. Stattdessen sollten Sie es so schreiben:

const char* myFunction()
{

    return "My String";

}

Und es ist immer gut, Ihren Typ zu qualifizieren, constwährend Sie Zeigern Literale in C zuweisen, da Literale in C nicht geändert werden können.

Cheshar
quelle
0

Ihr Funktionsprototyp gibt an, dass Ihre Funktion ein Zeichen zurückgibt. Daher können Sie in Ihrer Funktion keine Zeichenfolge zurückgeben.

user224579
quelle
0
char* myFunction()
{
    return "My String";
}

In C sind Zeichenfolgenliterale Arrays mit der Speicherklasse der statischen Konstanten, sodass die Rückgabe eines Zeigers auf dieses Array sicher ist. Weitere Details finden Sie in der Stapelüberlauf-Frage "Lebensdauer" eines Zeichenfolgenliteral in C.

Oleg Karavan
quelle
0

Zeichenfolge von Funktion zurückgeben

#include <stdio.h>

const char* greet() {
  return "Hello";
}

int main(void) {
  printf("%s", greet());
}
leidenschaftliche evops
quelle