Renvoyer une chaîne C à partir d'une fonction

109

J'essaie de renvoyer une chaîne C à partir d'une fonction, mais cela ne fonctionne pas. Voici mon code.

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

Dans mainje l'appelle comme ceci:

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

J'ai également essayé d'autres moyens myFunction, mais ils ne fonctionnent pas. Par exemple:

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

Remarque: je ne suis pas autorisé à utiliser des pointeurs!

Petit historique sur ce problème:

Il y a une fonction qui découvre de quel mois il s'agit. Par exemple, s'il vaut 1, il renvoie janvier, etc.

Alors , quand il va imprimer, ça fait comme ça: printf("Month: %s",calculateMonth(month));. Maintenant, le problème est de savoir comment renvoyer cette chaîne à partir de la calculateMonthfonction.

itsaboutcode
la source
10
Malheureusement, vous avez besoin de pointeurs dans ce cas.
Nick Bedford
1
@Hayato Eh bien, je crois que nous sommes des adultes ici et je sais qu'il devrait renvoyer 0, c'était juste pour donner l'exemple lox ..
itsaboutcode
3
return 0est implicite par défaut uniquement en C99 (et C ++) mais pas en C90.
hrnt
1
Ensuite, vous ne serez pas en mesure de le faire, à part des hacks stupides qui sont de toute façon des manipulations de pointeurs vraiment brisées. Les pointeurs existent pour une raison ...: |
GManNickG

Réponses:

223

Votre signature de fonction doit être:

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

Contexte:

C'est tellement fondamental pour C & C ++, mais peu de discussions supplémentaires devraient être en ordre.

En C (et C ++ d'ailleurs), une chaîne est juste un tableau d'octets terminé par un octet zéro - d'où le terme "chaîne-zéro" est utilisé pour représenter cette saveur particulière de chaîne. Il existe d'autres types de chaînes, mais en C (et C ++), cette saveur est intrinsèquement comprise par le langage lui-même. D'autres langages (Java, Pascal, etc.) utilisent différentes méthodologies pour comprendre "ma chaîne".

Si jamais vous utilisez l'API Windows (qui est en C ++), vous verrez assez régulièrement des paramètres de fonction comme: "LPCSTR lpszName". La partie 'sz' représente cette notion de 'string-zero': un tableau d'octets avec un terminateur nul (/ zéro).

Clarification:

Pour cette «intro», j'utilise les mots «octets» et «caractères» de manière interchangeable, car c'est plus facile à apprendre de cette façon. Sachez qu'il existe d'autres méthodes (caractères larges et systèmes de caractères multi-octets ( mbcs )) qui sont utilisées pour gérer les caractères internationaux. UTF-8 est un exemple de mbcs. Par souci d'intro, je «saute» tranquillement tout cela.

Mémoire:

Cela signifie qu'une chaîne comme "ma chaîne" utilise en fait 9 + 1 (= 10!) Octets. Il est important de savoir quand vous parvenez enfin à allouer des chaînes de manière dynamique.

Donc, sans ce «zéro final», vous n'avez pas de chaîne. Vous avez un tableau de caractères (également appelé tampon) en mémoire.

Longévité des données:

L'utilisation de la fonction de cette façon:

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

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

... vous amènera généralement avec des exceptions aléatoires non gérées / des défauts de segment et autres, en particulier `` sur la route ''.

En bref, bien que ma réponse soit correcte - 9 fois sur 10, vous vous retrouverez avec un programme qui plante si vous l'utilisez de cette façon, surtout si vous pensez que c'est une «bonne pratique» de le faire de cette façon. En bref: ce n'est généralement pas le cas.

Par exemple, imaginez dans le futur, la chaîne doit maintenant être manipulée d'une manière ou d'une autre. Généralement, un codeur `` empruntera le chemin facile '' et (essaiera) d'écrire du code comme celui-ci:

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

Autrement dit, votre programme plantera car le compilateur (peut / peut ne pas) avoir libéré la mémoire utilisée szBufferpar le moment où le printf()in main()est appelé. (Votre compilateur doit également vous avertir de tels problèmes au préalable.)

Il existe deux façons de renvoyer des chaînes qui ne barfront pas si facilement.

  1. renvoyant des tampons (statiques ou alloués dynamiquement) qui durent un certain temps. En C ++, utilisez des 'classes d'assistance' (par exemple,std::string ) pour gérer la longévité des données (ce qui nécessite de changer la valeur de retour de la fonction), ou
  2. passer un tampon à la fonction qui se remplit d'informations.

Notez qu'il est impossible d'utiliser des chaînes sans utiliser de pointeurs en C. Comme je l'ai montré, ils sont également. Même en C ++ avec des classes de modèle, des tampons (c'est-à-dire des pointeurs) sont toujours utilisés en arrière-plan.

Donc, pour mieux répondre à la (question maintenant modifiée). (Il y aura sûrement une variété d '«autres réponses» qui peuvent être fournies.)

Des réponses plus sûres:

Exemple 1, en utilisant des chaînes allouées statiquement:

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

Ce que fait le «statique» ici (de nombreux programmeurs n'aiment pas ce type d '«allocation»), c'est que les chaînes sont placées dans le segment de données du programme. Autrement dit, il est alloué en permanence.

Si vous passez au C ++, vous utiliserez des stratégies similaires:

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

... mais il est probablement plus facile d'utiliser des classes d'assistance, par exemple std::string, si vous écrivez le code pour votre propre usage (et ne fait pas partie d'une bibliothèque à partager avec d'autres).

Exemple 2, utilisant des tampons définis par l'appelant:

C'est la manière la plus «infaillible» de passer des chaînes. Les données renvoyées ne sont pas sujettes à manipulation par l'appelant. Autrement dit, l'exemple 1 peut facilement être abusé par un appelant et vous exposer à des défauts d'application. De cette façon, c'est beaucoup plus sûr (bien qu'il utilise plus de lignes de code):

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

Il y a de nombreuses raisons pour lesquelles la deuxième méthode est meilleure, en particulier si vous écrivez une bibliothèque pour être utilisée par d'autres (vous n'avez pas besoin de vous verrouiller dans un schéma d'allocation / désallocation particulier, les tiers ne peuvent pas casser votre code, et vous n'avez pas besoin de créer un lien vers une bibliothèque de gestion de mémoire spécifique), mais comme tout code, c'est à vous de décider de ce que vous préférez. Pour cette raison, la plupart des gens optent pour l'exemple 1 jusqu'à ce qu'ils aient été brûlés tellement de fois qu'ils refusent de l'écrire de cette façon;)

Avertissement:

J'ai pris ma retraite il y a plusieurs années et mon C est un peu rouillé maintenant. Ce code de démonstration devrait tous se compiler correctement avec C (c'est bien pour n'importe quel compilateur C ++).

cmroanirgo
la source
2
En fait, la fonction doit retourner a char *, car les littéraux de chaîne en C sont de type char[]. Cependant, ils ne doivent en aucun cas être modifiés, le retour const char*est donc préférable (voir securecoding.cert.org/confluence/x/mwAV ). Un retour char *peut être nécessaire si la chaîne sera utilisée dans une fonction de bibliothèque héritée ou externe qui (malheureusement) attend un char*argument en tant que, même si elle ne lira qu'à partir de celui-ci. C ++, d'autre part, a des littéraux de chaîne de const char[]type (et, depuis C ++ 11, vous pouvez également avoir des std::stringlittéraux).
TManhente
17
@cmroanirgo le préfixe my déclare au lecteur que la fonction a été créée par l'utilisateur. Je trouve parfaitement raisonnable de l'utiliser dans un tel contexte.
quant
4
selon ici: stackoverflow.com/questions/9970295/… , vous pouvez renvoyer une chaîne littérale
giorgim
6
Le code marqué fraught with problemsdans la section «Longévité des données» est en fait parfaitement valide. Les littéraux de chaîne ont des durées de vie statiques en C / C ++. Voir le lien mentionné par Giorgi ci-dessus.
chengiz
1
@cmroanirgo Renvoyer des chaînes littérales est une bonne pratique et un bon style. Ce n'est pas "plein de problèmes", et il ne plantera pas 9 fois sur 10: il ne plantera jamais. Même les compilateurs des années 80 (du moins ceux que j'ai utilisés) prennent correctement en charge la durée de vie illimitée des littéraux de chaîne. Remarque: je ne suis pas sûr de ce que vous vouliez dire à propos de la modification de la réponse: je vois toujours qu'il est sujet aux plantages.
cesse
12

La chaîne AC est définie comme un pointeur vers un tableau de caractères.

Si vous ne pouvez pas avoir de pointeurs, par définition, vous ne pouvez pas avoir de chaînes.

Crashworks
la source
Vous pouvez passer dans un tableau à une fonction, puis agir sur ce tableau: void foo( char array[], int length). Bien sûr, arrayc'est un pointeur sous le capot, mais ce n'est pas "explicitement" un pointeur, et il peut donc être plus intuitif pour quelqu'un qui apprend les tableaux mais qui n'a pas tout à fait appris les pointeurs.
jvriesem le
12

Notez cette nouvelle fonction:

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

J'ai défini «tableau» comme statique. Sinon, lorsque la fonction se termine, la variable (et le pointeur que vous renvoyez) sort de la portée. Depuis que la mémoire est allouée sur la pile, et il sera corrompue. L'inconvénient de cette implémentation est que le code n'est ni réentrant ni threadsafe.

Une autre alternative serait d'utiliser malloc pour allouer la chaîne dans le tas, puis de libérer sur les emplacements corrects de votre code. Ce code sera réentrant et threadsafe.

Comme indiqué dans le commentaire, c'est une très mauvaise pratique, car un attaquant peut alors injecter du code dans votre application (il / elle doit ouvrir le code en utilisant GDB, puis faire un point d'arrêt et modifier la valeur d'une variable retournée pour déborder et le plaisir ne fait que commencer).

Il est beaucoup plus recommandé de laisser l'appelant gérer les allocations de mémoire. Voir ce nouvel exemple:

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

Notez que le seul contenu qui peut être modifié est celui que l'utilisateur. Un autre effet secondaire - ce code est désormais threadsafe, du moins du point de vue de la bibliothèque. Le programmeur appelant cette méthode doit vérifier que la section mémoire utilisée est threadsafe.

elcuco
la source
2
C'est généralement une mauvaise façon de faire les choses. Le char * peut être manipulé par le code environnant. Autrement dit, vous pouvez faire des choses comme ceci: strcpy (maFonction (), "Une chaîne vraiment longue"); et votre programme plantera en raison d'une violation d'accès.
cmroanirgo
Quelque chose manque près de "celui que l'utilisateur" .
Peter Mortensen le
8

Votre problème concerne le type de retour de la fonction - il doit être:

char *myFunction()

... et alors votre formulation originale fonctionnera.

Notez que vous ne pouvez pas avoir de chaînes C sans que des pointeurs soient impliqués, quelque part le long de la ligne.

Aussi: augmentez les avertissements de votre compilateur. Il aurait dû vous avertir de la conversion de la ligne de retour a char *en charsans conversion explicite.

caf
la source
1
Je pense que la signature devrait const char * puisque la chaîne est un littéral mais si je ne me trompe pas, le compilateur acceptera cela.
Luke
5

Sur la base de votre histoire nouvellement ajoutée avec la question, pourquoi ne pas simplement renvoyer un entier de 1 à 12 pour le mois, et laisser la fonction main () utiliser une instruction switch ou if-else ladder pour décider quoi imprimer? Ce n'est certainement pas la meilleure façon de procéder - char * serait - mais dans le contexte d'une classe comme celle-ci, j'imagine que c'est probablement la plus élégante.

Twisol
la source
3

Vous pouvez créer le tableau dans l'appelant, qui est la fonction principale, et passer le tableau à l'appelé qui est votre myFunction (). Ainsi myFunction peut remplir la chaîne dans le tableau. Cependant, vous devez déclarer myFunction () comme

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

Et dans la fonction principale, myFunction doit être appelée de cette manière:

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

Cependant, un pointeur est toujours utilisé.

ChainLooper
la source
2

Le type de retour de votre fonction est un seul caractère ( char). Vous devez renvoyer un pointeur vers le premier élément du tableau de caractères. Si vous ne pouvez pas utiliser de pointeurs, vous êtes foutu. :(

hrnt
la source
2

Ou que diriez-vous de celui-ci:

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

Et appelez cela avec le mois que vous calculez ailleurs.

Sebastiaan M
la source
1
+1 pas ce que OP a demandé mais c'est probablement ce que la mission attend de vous, car il ne peut pas utiliser de pointeurs.
Vitim.us
Même printf utilise des pointeurs. Un pointeur est comme un couteau - essentiel pour vivre et travailler, mais vous devez le tenir par la poignée et utiliser le côté pointu pour couper avec ou vous allez passer un mauvais moment. Le malheureux placement d'espaces dans la définition de fonction est un bug cérébral pour de nombreux nouveaux programmeurs C. char * func (char * s); char func (char * s); char func * char * s); sont tous identiques mais ont tous un aspect différent, et pour compliquer la confusion, * est également l'opérateur de dé-référence pour les variables qui sont des pointeurs.
Chris Reid
1

A charn'est qu'un seul caractère d'un octet. Il ne peut pas stocker la chaîne de caractères, ni un pointeur (que vous ne pouvez apparemment pas avoir). Par conséquent, vous ne pouvez pas résoudre votre problème sans utiliser des pointeurs (qui char[]sont du sucre syntaxique pour).

Nick Bedford
la source
1

Si vous ne pouvez vraiment pas utiliser de pointeurs, faites quelque chose comme ceci:

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

Le chiffre magique 9 est horrible et ce n'est pas un exemple de bonne programmation. Mais vous obtenez le point. Notez que les pointeurs et les tableaux sont la même chose (en quelque sorte), donc c'est un peu de la triche.

Sebastiaan M
la source
Habituellement, si vous devez mettre en œuvre de telles solutions à des problèmes de devoirs, vos hypothèses préliminaires sont fausses.
hrnt
1

Eh bien, dans votre code, vous essayez de renvoyer un String(en C qui n'est rien d'autre qu'un tableau de caractères terminé par null), mais c'est le type de retour de votre fonction charqui vous cause tous les problèmes. Au lieu de cela, vous devriez l'écrire de cette façon:

const char* myFunction()
{

    return "My String";

}

Et il est toujours bon de qualifier votre type avec consttout en attribuant des littéraux en C aux pointeurs, car les littéraux en C ne sont pas modifiables.

Cheshar
la source
0

Votre prototype de fonction indique que votre fonction renverra un caractère. Ainsi, vous ne pouvez pas renvoyer une chaîne dans votre fonction.

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

En C, les littéraux de chaîne sont des tableaux avec la classe de mémoire constante statique, donc renvoyer un pointeur vers ce tableau est sûr. Plus de détails sont dans la question "Life-time" de Stack Overflow d'une chaîne littérale en C

Oleg Karavan
la source
0

Retourne la chaîne de la fonction

#include <stdio.h>

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

int main(void) {
  printf("%s", greet());
}
passionné
la source