Est-ce que le retour d’un pointeur sur une variable locale statique est sécurisé?

Je travaille avec un code qui utilise largement l’idiome de renvoyer un pointeur sur une variable locale statique. par exemple:

char* const GetSsortingng() { static char sTest[5]; strcpy(sTest, "Test"); return sTest; } 

Ai-je raison de penser que c’est sûr?

PS, je sais que ce serait une meilleure façon de faire la même chose:

 char* const GetSsortingng() { return "Test"; } 

Edit: Apologies, la signature de la fonction devrait bien entendu être:

 const char* GetSsortingng(); 

Premier exemple: un peu sûr

 char* const GetSsortingng() { static char sTest[5]; strcpy(sTest, "Test"); return sTest; } 

Bien que cela ne soit pas recommandé, ceci est sûr, la scope d’une variable statique rest active même lorsque la scope de la fonction se termine. Cette fonction n’est pas du tout sécurisée pour les threads. Une meilleure fonction vous amènerait à passer un char* buffer et un maxsize pour la fonction GetSsortingng() à remplir.

En particulier, cette fonction n’est pas considérée comme une fonction réentrante car les fonctions réentrantes ne doivent pas, entre autres, renvoyer l’adresse à des données statiques (globales) non constantes . Voir les fonctions réentrantes .

Deuxième exemple: complètement dangereux

 char* const GetSsortingng() { return "Test"; } 

Ce serait sûr si vous const char * un const char * . Ce que vous avez donné n’est pas sûr. La raison en est que les littéraux de chaîne peuvent être stockés dans un segment de mémoire en lecture seule et que leur modification entraîne des résultats indéfinis.

char* const (pointeur const) signifie que vous ne pouvez pas modifier l’adresse vers laquelle pointe le pointeur. const char * (pointeur sur const) signifie que vous ne pouvez pas modifier les éléments sur lesquels pointe ce pointeur.

Conclusion:

Vous devriez envisager soit:

1) Si vous avez access au code, modifiez le GetSsortingng pour prendre un paramètre d’un char* buffer à remplir et une maxsize à utiliser.

2) Si vous n’avez pas access au code, mais que vous devez l’appeler, placez cette méthode dans une autre fonction protégée par un mutex. La nouvelle méthode est décrite dans 1.

Fondamentalement, oui, c’est sûr dans la mesure où la valeur durera indéfiniment car elle est statique.

Ce n’est pas sûr dans le sens où vous avez renvoyé un pointeur constant vers des données variables, plutôt qu’un pointeur de variable vers des données constantes. Il est préférable que les fonctions d’appel ne soient pas autorisées à modifier les données:

 const char *GetSsortingng(void) { static char sTest[5]; strncpy(sTest, "Test", sizeof(sTest)-1); sTest[sizeof(sTest)-1] = '\0'; return sTest; } 

Dans le cas simple montré, il n’est pas nécessaire de s’inquiéter des débordements de tampon, bien que ma version du code m’inquiète et assure la terminaison nulle. Une alternative serait d’utiliser la fonction strcpy_s place:

 const char *GetSsortingng(void) { static char sTest[5]; strcpy_s(sTest, sizeof(sTest), "Test"); return sTest; } 

Plus important encore, les deux variantes renvoient un pointeur (variable) sur des données constantes, de sorte que l’utilisateur ne doit pas modifier la chaîne et (probablement) piétiner en dehors de la plage du tableau. (Comme @strager le souligne dans les commentaires, le fait de renvoyer un caractère const char * n’est pas une garantie que l’utilisateur ne tentera pas de modifier les données renvoyées. Cependant, il doit convertir le pointeur renvoyé pour qu’il ne modifie pas les données, cela invoque un comportement indéfini et tout est possible à ce stade.)

L’un des avantages du retour littéral est que la promesse de non-écriture peut généralement être appliquée par le compilateur et le système d’exploitation. La chaîne sera placée dans le segment text (code) du programme et le système d’exploitation générera une erreur (violation de segmentation sous Unix) si l’utilisateur tente de modifier les données pointées par la valeur renvoyée.

[Au moins une des autres réponses indique que le code n’est pas rentrant; c’est correct La version renvoyant le littéral est ré-entrant. Si la ré-appartenance est importante, l’interface doit être corrigée pour que l’appelant fournisse l’espace dans lequel les données sont stockées.]

Cela dépend de ce que vous entendez par sécurité. Il y a quelques problèmes que je peux voir immédiatement:

  1. Vous avez renvoyé un caractère char * const , qui permettra aux appelants de changer la chaîne à cet emplacement. Dépassement potentiel du tampon Ou avez-vous voulu dire un const char * ?
  2. Vous pourriez avoir un problème de réentrance ou de concurrence.

Pour expliquer le second, considérez ceci:

 const char * const format_error_message(int err) { static char error_message[MAXLEN_ERROR_MESSAGE]; sprintf(error_message, "Error %#x occurred", err); return error_message; } 

Si vous l’appelez comme ceci:

 int a = do_something(); int b = do_something_else(); if (a != 0 && b != 0) { fprintf(stderr, "do_something failed (%s) AND do_something_else failed (%s)\n", format_error_message(a), format_error_message(b)); } 

… qu’est-ce qui va être imprimé?

Même chose pour le filetage.

static variables static (dans une fonction) sont comme des variables globales. En général, ils doivent être évités (comme les variables globales, ils provoquent des problèmes de ré-appartenance), mais sont parfois utiles (certaines fonctions standard de la bibliothèque les utilisent). Vous pouvez renvoyer des pointeurs vers des variables globales afin que vous puissiez également renvoyer des pointeurs vers static variables static .

Oui, c’est parfaitement sûr. La durée de vie de la statique locale est celle de l’exécution complète du programme en C. Vous pouvez donc y retourner un pointeur, car le tableau sera actif même après le retour de la fonction, et le pointeur renvoyé pourra être correctement référencé.

C’est très utile, car vous pouvez utiliser la fonction directement en tant que paramètre printf. Mais, comme cela a été mentionné, les appels multiples à la fonction dans un seul appel poseront un problème, car la fonction utilise le même stockage et l’appel deux fois écrasera la chaîne renvoyée. Mais j’ai testé ce morceau de code et cela semble fonctionner – vous pouvez appeler une fonction en toute sécurité, où givemessortingng est utilisé au maximum à MAX_CALLS et il se comportera correctement.

 #define MAX_CALLS 3 #define MAX_LEN 30 char *givemessortingng(int num) { static char buf[MAX_CALLS][MAX_LEN]; static int rotate=0; rotate++; rotate%=sizeof(buf)/sizeof(buf[0]); sprintf(buf[rotate],"%d",num); return buf[rotate]; } 

Le seul problème est la sécurité des threads, mais cela peut être résolu avec des variables locales de thread (le mot clé __thread de gcc).

Oui, cette option est fréquemment utilisée pour renvoyer la partie textuelle d’une recherche, c’est-à-dire pour traduire un numéro d’erreur en une chaîne conviviale.

Il est sage de le faire dans les cas où vous auriez:

 fprintf(stderr, "Error was %s\n", my_ssortingng_to_error(error_code)); 

Si my_ssortingng_to_error() renvoyé une chaîne allouée, votre programme fuirait étant donné l’utilisation (très) commune d’une telle fonction.

 char const *foo_error(...) { return "Mary Poppins"; } 

… est également correct, certains compilateurs de cérébraux morts pourraient vouloir que vous le lanciez.

Regardez simplement les chaînes de cette façon, ne retournez pas un livre 🙂