140 votes

Renvoi d'une chaîne de caractères C à partir d'une fonction

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

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

En main Je l'appelle ainsi :

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

J'ai également essayé d'autres méthodes pour myFunction mais ils ne fonctionnent pas. Par exemple :

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

Note : Je n'ai pas le droit d'utiliser des pointeurs !

Un peu d'histoire sur ce problème :

Il existe une fonction qui permet de savoir quel mois nous sommes. Par exemple, si c'est 1, elle renvoie janvier, etc.

Ainsi, lorsqu'il s'agit d'imprimer, il procède de la manière suivante : printf("Month: %s",calculateMonth(month)); . Le problème est maintenant de savoir comment renvoyer cette chaîne de caractères à partir de la fonction calculateMonth fonction.

268voto

cmroanirgo Points 2900

Votre signature de fonction doit être :

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

Contexte :

Il s'agit d'un élément fondamental du C et du C++, mais il convient d'en parler un peu plus longuement.

En C (et en C++ d'ailleurs), une chaîne de caractères n'est qu'un tableau d'octets terminé par un octet zéro - c'est pourquoi le terme "chaîne-zéro" est utilisé pour représenter cette forme particulière de chaîne de caractères. Il existe d'autres types de chaînes, mais en C (et C++), ce type de chaîne est compris de manière inhérente par le langage lui-même. D'autres langages (Java, Pascal, etc.) utilisent des méthodologies différentes pour comprendre les chaînes de caractères. "my string" .

Si vous utilisez l'API Windows (qui est en C++), vous verrez 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 null (/zero).

Clarification :

Pour les besoins de cette "introduction", j'utilise indifféremment les mots "octets" et "caractères", parce qu'il est plus facile d'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és pour traiter les caractères internationaux. UTF-8 est un exemple de SCBM. Pour les besoins de l'introduction, je passe tranquillement sur tout cela.

Mémoire :

Cela signifie qu'une chaîne de caractères comme "my string" utilise en fait 9+1 (=10 !) octets. Il est important de le savoir lorsque vous vous apprêtez enfin à allouer des chaînes de caractères de manière dynamique.

Ainsi, sans ce "zéro final", il n'y a pas de chaîne de caractères. Vous avez un tableau de caractères (également appelé tampon) qui traîne en mémoire.

Longévité des données :

L'utilisation de la fonction se fait de cette manière :

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

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

... vous conduira généralement à des exceptions non gérées aléatoires, à des erreurs de segment et à d'autres problèmes de ce type, en particulier "à terme".

En bref, bien que ma réponse soit correcte, 9 fois sur 10, vous finirez par avoir un programme qui se plante si vous l'utilisez de cette manière, surtout si vous pensez que c'est une "bonne pratique" de le faire de cette manière. En bref : ce n'est généralement pas le cas.

Par exemple, imaginons qu'à un moment donné dans le futur, la chaîne de caractères actuelle doive être manipulée d'une manière ou d'une autre. Généralement, un codeur va "prendre le chemin le plus facile" et (essayer) d'écrire un code comme celui-ci :

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

En d'autres termes, votre programme se plantera parce que le compilateur (peut ou ne peut pas) a libéré la mémoire utilisée par szBuffer au moment où le printf() en main() est appelé. (Votre compilateur devrait également vous avertir de ces problèmes à l'avance).

Il y a deux façons de renvoyer des chaînes qui ne dégueulent pas si facilement.

  1. retourner des tampons (statiques ou alloués dynamiquement) qui restent en place pendant un certain temps. En C++, on utilise des "classes d'aide" (par exemple, std::string ) pour gérer la longévité des données (ce qui nécessite de modifier la valeur de retour de la fonction), ou
  2. transmettre à la fonction une mémoire tampon qui sera remplie d'informations.

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

Ainsi, pour mieux répondre à la question (maintenant modifiée). (Il y a certainement une variété d'"autres réponses" qui peuvent être fournies).

Des réponses plus sûres :

Exemple 1 : utilisation de chaînes de caractères allouées de manière statique :

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

Quel est le static (de nombreux programmeurs n'aiment pas ce type d'"allocation") est que les chaînes de caractères sont placées dans le segment de données du programme. En d'autres termes, elles sont allouées de manière permanente.

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'aide, telles que std::string si vous écrivez le code pour votre propre usage (et non dans le cadre d'une bibliothèque destinée à être partagée avec d'autres).

Exemple 2 : utilisation de tampons définis par l'appelant :

C'est la façon la plus sûre de faire circuler les chaînes de caractères. Les données renvoyées ne sont pas susceptibles d'être manipulées par la partie appelante. En d'autres termes, l'exemple 1 peut facilement être utilisé de manière abusive par une partie appelante et vous exposer à des fautes d'application. Cette méthode est beaucoup plus sûre (bien qu'elle 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 destinée à être utilisée par d'autres (vous n'avez pas besoin de vous enfermer dans un schéma d'allocation/désallocation particulier, les tiers ne peuvent pas casser votre code, et vous n'avez pas besoin de vous lier à une bibliothèque de gestion de la mémoire spécifique), mais comme pour tout code, c'est à vous de choisir ce qui vous convient le mieux. 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 d'écrire de cette façon ;)

Avis de non-responsabilité :

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 se compiler correctement en C (il est cependant correct pour n'importe quel compilateur C++).

14voto

Crashworks Points 22920

Une chaîne C est définie comme un pointeur sur un tableau de caractères.

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

13voto

elcuco Points 3953

Notez cette nouvelle fonction :

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

J'ai défini le terme "tableau" comme étant statique. Sinon, lorsque la fonction se termine, la variable (et le pointeur que vous renvoyez) sort du champ d'application. Puisque cette mémoire est allouée sur la pile, et qu'elle volonté se corrompent. L'inconvénient de cette implémentation est que le code n'est pas réentrant et n'est pas sûr pour les threads.

Une autre solution consisterait à utiliser malloc pour allouer la chaîne de caractères dans le tas, puis la libérer aux endroits appropriés de votre code. Ce code sera réentrant et threadsafe.

Comme indiqué dans le commentaire, il s'agit d'une très mauvaise pratique, car un attaquant peut alors injecter du code dans votre application (il lui suffit d'ouvrir le code à l'aide de GDB, puis de créer un point d'arrêt et de modifier la valeur d'une variable renvoyée pour qu'elle déborde, et le plaisir commence).

Il est beaucoup plus recommandé de laisser l'appelant s'occuper des 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 de l'utilisateur. Autre effet secondaire : ce code est désormais sûr pour les threads, du moins du point de vue de la bibliothèque. Le programmeur qui appelle cette méthode doit vérifier que la section de mémoire utilisée est threadsafe.

8voto

caf Points 114951

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

char *myFunction()

...et alors votre formulation originale fonctionnera.

Notez que vous ne peut pas ont des chaînes de caractères en C sans que des pointeurs soient impliqués, à un moment ou à un autre.

En outre : Augmentez les avertissements de votre compilateur. Il aurait dû vous avertir à propos de cette ligne de retour convertissant un char * à char sans qu'une distribution explicite ne soit nécessaire.

5voto

Twisol Points 1061

En se basant sur votre histoire nouvellement ajoutée à la question, pourquoi ne pas simplement retourner un entier de 1 à 12 pour le mois, et laisser la fonction main() utiliser une instruction switch ou une échelle if-else pour décider de ce qui doit être imprimé ? Ce n'est certainement pas la meilleure façon de procéder - char* le serait - mais dans le contexte d'une classe comme celle-ci, j'imagine que c'est probablement la plus élégante.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X