web-dev-qa-db-fra.com

Le renvoi d'un pointeur vers une variable locale statique est-il sûr?

Je travaille avec du code qui utilise largement l'idiome de retour d'un pointeur vers une variable locale statique. par exemple:

char* const GetString()
{
  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 GetString()
{
  return "Test";
}

Edit: Toutes mes excuses, la signature de la fonction doit bien sûr être:

const char* GetString();
43
John Carter

Premier exemple: assez sûr

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

Bien que cela ne soit pas recommandé, cela est sûr, la portée d'une variable statique reste active même lorsque la portée de la fonction se termine. Cette fonction n'est pas du tout très thread-safe. Une meilleure fonction vous permettrait de passer un char* buffer Et un maxsize pour que la fonction GetString() se remplisse.

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 fonctions réentrantes .

Deuxième exemple: complètement dangereux

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

Ce serait sûr si vous faisiez 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înera 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 vers const) signifie que vous ne pouvez pas modifier les éléments vers lesquels pointe ce pointeur.

Conclusion:

Vous devriez considérer soit:

1) Si vous avez accès au code, modifiez le GetString pour prendre un paramètre d'un char* buffer À remplir et un maxsize à utiliser.

2) Si vous n'avez pas accès au code mais que vous devez l'appeler, encapsulez cette méthode dans une autre fonction protégée par un mutex. La nouvelle méthode est celle décrite en 1.

37
Brian R. Bondy

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

Il 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 variable vers des données constantes. Il vaut mieux que les fonctions appelantes ne soient pas autorisées à modifier les données:

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

Dans le cas simple illustré, il n'est guère nécessaire de s'inquiéter des débordements de tampon, bien que ma version du code s'inquiète et assure une terminaison nulle. Une alternative serait d'utiliser la fonction TR24731strcpy_s au lieu:

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

Plus important encore, les deux variantes renvoient un pointeur (variable) vers des données constantes, de sorte que l'utilisateur ne devrait pas aller modifier la chaîne et (probablement) piétiner en dehors de la plage du tableau. (Comme le souligne @strager dans les commentaires, renvoyer un const char * n'est pas une garantie que l'utilisateur n'essaiera pas de modifier les données renvoyées. Cependant, ils doivent transtyper le pointeur renvoyé pour qu'il ne soit pas constant, puis modifier les données; cela appelle un comportement indéfini et tout est possible à ce stade.)

Un avantage 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 de texte (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 de retour.

[Au moins une des autres réponses note que le code n'est pas rentrant; c'est exact. La version renvoyant le littéral est rentrante. Si la rentrée est importante, l'interface doit être corrigée afin que l'appelant fournisse l'espace où les données sont stockées.]

9
Jonathan Leffler

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

8
strager

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 char * const, qui permettra aux appelants de modifier la chaîne à cet emplacement. Dépassement potentiel du tampon. Ou vouliez-vous dire un const char *?
  2. Vous pourriez avoir un problème de réentrée ou de simultanéité.

Pour expliquer la seconde, 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é?

Idem pour le filetage.

8
Roger Lipscombe

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 lui renvoyer un pointeur, car le tableau sera vivant même après le retour de la fonction, et le pointeur renvoyé peut être correctement dé-référencé.

2

C'est très utile, car vous pouvez utiliser la fonction directement comme paramètre printf. Mais, comme cela a été mentionné, plusieurs appels à la fonction dans un seul appel provoqueront un problème, car la fonction utilise le même stockage et l'appel deux fois écrasera la chaîne retournée. Mais j'ai testé ce morceau de code et cela semble fonctionner - vous pouvez appeler une fonction en toute sécurité, où givemestring est utilisé la plupart du temps MAX_CALLS et il se comportera correctement.

#define MAX_CALLS 3
#define MAX_LEN 30

char *givemestring(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 threads (mot clé __thread de gcc)

1
Nuclear

Oui, cela est fréquemment utilisé pour renvoyer la partie texte d'une recherche, c'est-à-dire pour traduire un certain nombre d'erreur en une chaîne conviviale.

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

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

Si my_string_to_error() renvoyait une chaîne allouée, votre programme fuirait étant donné l'utilisation (très) courante ci-dessus d'une telle fonction.

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

... est également OK, certains compilateurs sans cervelle voudront peut-être que vous le lanciez.

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

0
Tim Post