Restituzione di una stringa C da una funzione


109

Sto cercando di restituire una stringa C da una funzione, ma non funziona. Ecco il mio codice.

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

In mainChiedo in questo modo:

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

Ho anche provato altri modi per myFunction, ma non funzionano. Per esempio:

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

Nota: non sono autorizzato a usare i puntatori!

Poco background su questo problema:

C'è una funzione che sta scoprendo quale mese è. Ad esempio, se è 1, restituisce gennaio, ecc.

Così, quando si sta per stampare, che sta facendo in questo modo: printf("Month: %s",calculateMonth(month));. Ora il problema è come restituire quella stringa dalla calculateMonthfunzione.


10
Sfortunatamente in questo caso hai bisogno di suggerimenti.
Nick Bedford,

1
@ Hayato Beh, credo che siamo adulti qui e so che dovrebbe restituire 0, era solo per il gusto di dare un esempio
lox

3
return 0è implicito per impostazione predefinita solo in C99 (e C ++) ma non in C90.
hrnt

1
Allora non sarai in grado di farlo, a parte gli stupidi hack che in realtà hanno solo interrotto le manipolazioni del puntatore comunque. I puntatori esistono per una ragione ...: |
GManNickG

Risposte:


222

La firma della tua funzione deve essere:

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

Sfondo:

È così fondamentale per C e C ++, ma dovrebbero essere necessarie altre discussioni.

In C (e C ++ per quella materia), una stringa è solo un array di byte terminato con zero byte - quindi il termine "stringa zero" è usato per rappresentare questo particolare sapore di stringa. Esistono altri tipi di stringhe, ma in C (e C ++) questo aspetto è compreso intrinsecamente dal linguaggio stesso. Altri linguaggi (Java, Pascal, ecc.) Utilizzano metodologie differenti per comprendere "la mia stringa".

Se usi mai l'API di Windows (che è in C ++), vedrai parametri di funzione abbastanza regolari come: "LPCSTR lpszName". La parte "sz" rappresenta questa nozione di "stringa zero": un array di byte con un terminatore nullo (/ zero).

Una precisazione:

Per il bene di questa "introduzione", uso la parola "byte" e "caratteri" in modo intercambiabile, perché è più facile imparare in questo modo. Tenere presente che esistono altri metodi (caratteri larghi e sistemi di caratteri multibyte ( mbcs )) utilizzati per gestire i caratteri internazionali. UTF-8 è un esempio di mbcs. Per motivi di intro, "salto" tranquillamente tutto questo.

Memoria:

Ciò significa che una stringa come "la mia stringa" utilizza effettivamente 9 + 1 (= 10!) Byte. Questo è importante per sapere quando finalmente si arriva ad allocare le stringhe in modo dinamico.

Quindi, senza questo 'zero che termina', non hai una stringa. Hai un array di caratteri (chiamato anche buffer) in giro nella memoria.

Longevità dei dati:

L'uso della funzione in questo modo:

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

int main()
{
    const char* szSomeString = myFunction(); // Fraught with problems
    printf("%s", szSomeString);
}

... generalmente ti atterreranno con eccezioni casuali non gestite / errori di segmento e simili, specialmente 'lungo la strada'.

In breve, anche se la mia risposta è corretta, 9 volte su 10 vi ritroverete con un programma che va in crash se lo usate in quel modo, soprattutto se pensate che sia "buona pratica" farlo in quel modo. In breve: generalmente non lo è.

Ad esempio, immagina un po 'di tempo nel futuro, la stringa ora deve essere manipolata in qualche modo. Generalmente, un programmatore 'prenderà il percorso facile' e (proverà a) scrivere codice come questo:

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

Cioè, il programma vada in crash perché il compilatore (può / non può) hanno rilasciato la memoria utilizzata da szBufferdal momento in cui l' printf()in main()si chiama. (Il tuo compilatore dovrebbe anche avvisarti in anticipo di tali problemi.)

Ci sono due modi per restituire stringhe che non sbufferanno così facilmente.

  1. restituendo buffer (statici o allocati dinamicamente) che vivono per un po '. In C ++ usa 'classi di supporto' (ad esempio, std::string) per gestire la longevità dei dati (che richiede la modifica del valore di ritorno della funzione), o
  2. passare un buffer alla funzione che viene riempita di informazioni.

Notare che è impossibile usare le stringhe senza usare i puntatori in C. Come ho mostrato, sono anche sinonimi. Anche in C ++ con classi template, ci sono sempre buffer (cioè puntatori) usati in background.

Quindi, per rispondere meglio alla (domanda ora modificata). (Ci sono sicuramente una serie di "altre risposte" che possono essere fornite.)

Risposte più sicure:

Esempio 1, utilizzando stringhe allocate staticamente:

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"
}

Ciò che lo "statico" fa qui (a molti programmatori non piace questo tipo di "allocazione") è che le stringhe vengono inserite nel segmento di dati del programma. Cioè, è assegnato in modo permanente.

Se passi a C ++ utilizzerai strategie simili:

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;
    }
}

... ma probabilmente è più facile usare classi helper, come std::stringse stai scrivendo il codice per il tuo uso personale (e non fa parte di una libreria da condividere con altri).

Esempio 2, utilizzo di buffer definiti dal chiamante:

Questo è il modo più "infallibile" di passare le stringhe. I dati restituiti non sono soggetti a manipolazione da parte del chiamante. Cioè, l'esempio 1 può essere facilmente abusato da un chiamante e esporre l'utente a errori dell'applicazione. In questo modo, è molto più sicuro (anche se utilizza più righe di codice):

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"
}

Ci sono molti motivi per cui il secondo metodo è migliore, in particolare se stai scrivendo una libreria che deve essere utilizzata da altri (non è necessario bloccare un particolare schema di allocazione / deallocazione, terze parti non possono rompere il tuo codice, e non è necessario collegarsi a una specifica libreria di gestione della memoria), ma come tutto il codice, dipende da te ciò che ti piace di più. Per questo motivo, la maggior parte delle persone sceglie l'esempio 1 fino a quando non è stata bruciata così tante volte che si rifiuta più di scriverlo in quel modo;)

Disclaimer:

Sono andato in pensione diversi anni fa e il mio C è un po 'arrugginito ora. Questo codice demo dovrebbe essere compilato correttamente con C (va bene per qualsiasi compilatore C ++).


2
In realtà, la funzione deve restituire a char *, poiché i letterali stringa in C sono di tipo char[]. Tuttavia, non devono essere modificati in alcun modo, quindi const char*è preferibile restituirli (vedere securecoding.cert.org/confluence/x/mwAV ). La restituzione char *potrebbe essere necessaria se la stringa verrà utilizzata in una funzione di libreria legacy o esterna che (sfortunatamente) si aspetta un char*argomento, anche se leggerà solo da esso. C ++, d'altra parte, ha letterali stringa di const char[]tipo (e, dal momento che C ++ 11, puoi anche avere std::stringletterali).
TManhente

17
@cmroanirgo il prefisso my dichiara al lettore che la funzione è stata creata dall'utente. Lo trovo perfettamente ragionevole da usare in un tale contesto.
quant

4
secondo qui: stackoverflow.com/questions/9970295/… , puoi restituire una stringa letterale
giorgim

6
Il codice contrassegnato fraught with problemsnella sezione "Longevità dei dati" è effettivamente perfettamente valido. I valori letterali stringa hanno una durata statica in C / C ++. Vedi il link che Giorgi cita sopra.
chengiz

1
@cmroanirgo Restituire stringhe letterali è una buona pratica e un buon stile. Non è "irto di problemi" e non andrà in crash 9 volte su 10: non andrà mai in crash. Anche i compilatori degli anni '80 (almeno quelli che ho usato) supportano correttamente la durata illimitata delle stringhe letterali. Nota: non sono sicuro di cosa intendevi riguardo alla modifica della risposta: vedo ancora che dice che è soggetto a crash.
termina

12

La stringa AC è definita come un puntatore a una matrice di caratteri.

Se non puoi avere puntatori, per definizione non puoi avere stringhe.


È possibile passare in una matrice a una funzione e quindi operare su tale array: void foo( char array[], int length). Ovviamente, arrayè un puntatore nascosto, ma non è "esplicitamente" un puntatore, e potrebbe quindi essere più intuitivo per qualcuno che impara gli array ma che non ha ancora imparato i puntatori.
jvriesem

12

Nota questa nuova funzione:

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

Ho definito "array" come statico. Altrimenti, quando la funzione termina, la variabile (e il puntatore che stai restituendo) esce dall'ambito. Poiché quella memoria è allocata nello stack e verrà danneggiata. Lo svantaggio di questa implementazione è che il codice non è rientrante e non è sicuro per i thread.

Un'altra alternativa sarebbe usare malloc per allocare la stringa nell'heap, quindi liberare nelle posizioni corrette del codice. Questo codice sarà rientrante e sicuro per i thread.

Come notato nel commento, questa è una pessima pratica, poiché un utente malintenzionato può quindi iniettare codice nella tua applicazione (deve aprire il codice utilizzando GDB, quindi creare un punto di interruzione e modificare il valore di una variabile restituita in overflow e il divertimento è appena iniziato).

È molto più consigliato lasciare che il chiamante gestisca le allocazioni di memoria. Guarda questo nuovo esempio:

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;
}

Si noti che l'unico contenuto che può essere modificato è quello dell'utente. Un altro effetto collaterale: questo codice ora è sicuro per i thread, almeno dal punto di vista della libreria. Il programmatore che chiama questo metodo dovrebbe verificare che la sezione di memoria utilizzata sia sicura per i thread.


2
Questo è generalmente un brutto modo di affrontare le cose. Il carattere * può essere manipolato dal codice circostante. Cioè, puoi fare cose come questa: strcpy (myFunction (), "A really long string"); e il tuo programma andrà in crash a causa di una violazione di accesso.
cmroanirgo

Manca qualcosa vicino a "quello che l'utente" .
Peter Mortensen

8

Il tuo problema è con il tipo di ritorno della funzione - deve essere:

char *myFunction()

... e poi la tua formulazione originale funzionerà.

Nota che non puoi avere stringhe C senza puntatori coinvolti, da qualche parte lungo la linea.

Inoltre: alza gli avvisi del compilatore. Avrebbe dovuto avvisarti di quella linea di ritorno che converte un char *in charsenza un cast esplicito.


1
Penso che la firma dovrebbe const char * poiché la stringa è letterale ma se non sbaglio il compilatore lo accetterà.
Luca

5

Sulla base del tuo retroscena appena aggiunto con la domanda, perché non restituire un numero intero da 1 a 12 per il mese e lasciare che la funzione main () usi un'istruzione switch o una ladder if-else per decidere cosa stampare? Certamente non è il modo migliore per andare - char * sarebbe - ma nel contesto di una classe come questa immagino che sia probabilmente la più elegante.


3

È possibile creare l'array nel chiamante, che è la funzione principale, e passare l'array al chiamato che è myFunction (). Pertanto myFunction può riempire la stringa nell'array. Tuttavia, è necessario dichiarare myFunction () come

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

E nella funzione principale, myFunction dovrebbe essere chiamata in questo modo:

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') */

Tuttavia, viene ancora utilizzato un puntatore.


2

Il tipo di ritorno della funzione è un singolo carattere ( char). Dovresti restituire un puntatore al primo elemento della matrice di caratteri. Se non puoi usare i puntatori, sei fottuto. :(


2

O che ne pensi di questo:

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

E chiamalo con il mese in cui calcoli da qualche altra parte.


1
+1 non è quello che ha chiesto OP, ma questo è probabilmente ciò che il compito si aspetta che tu faccia, dal momento che non può usare i puntatori.
Vitim.us

Anche printf usa i puntatori. Un puntatore è come un coltello: essenziale per vivere e lavorare, ma devi tenerlo per il manico e usare il lato affilato per tagliare o passerai un brutto momento. La sfortunata collocazione di spazi nella definizione della funzione è un bug cerebrale per molti nuovi programmatori C. char * func (char * s); char func (char * s); char func * char * s); sono tutti uguali ma hanno un aspetto diverso e, per creare confusione, * è anche l'operatore di de-riferimento per le variabili che sono puntatori.
Chris Reid

1

A charè solo un singolo carattere di un byte. Non può memorizzare la stringa di caratteri, né è un puntatore (che apparentemente non puoi avere). Pertanto non puoi risolvere il tuo problema senza usare i puntatori (che char[]è zucchero sintattico per).


1

Se davvero non puoi usare i puntatori, fai qualcosa del genere:

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;
}

Il numero magico 9 è orribile e questo non è un esempio di buona programmazione. Ma hai capito il punto. Nota che i puntatori e gli array sono la stessa cosa (più o meno), quindi questo è un po 'barare.


Di solito, se è necessario implementare tali soluzioni ai problemi dei compiti a casa, le ipotesi preliminari sono sbagliate.
hrnt

1

Bene, nel tuo codice stai cercando di restituire un String(in C che non è altro che un array di caratteri terminato da null), ma il tipo di ritorno della tua funzione è charche ti sta causando tutti i problemi. Invece dovresti scriverlo in questo modo:

const char* myFunction()
{

    return "My String";

}

Ed è sempre bene qualificare il tuo tipo constmentre assegni i letterali in C ai puntatori poiché i letterali in C non sono modificabili.


0

Il tuo prototipo di funzione afferma che la tua funzione restituirà un carattere. Pertanto, non puoi restituire una stringa nella tua funzione.


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

In C, i valori letterali stringa sono array con la classe di memoria costante statica, quindi restituire un puntatore a questo array è sicuro. Maggiori dettagli sono nella domanda di Stack Overflow "Durata" di una stringa letterale in C


0

Restituisce una stringa dalla funzione

#include <stdio.h>

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

int main(void) {
  printf("%s", greet());
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.