105 votes

"Durée de vie" d'une chaîne littérale en C

Le pointeur retourné par la fonction suivante ne serait-il pas inaccessible?

char *foo(int rc)
{
    switch (rc)
    {
        case 1:

            return("one");

        case 2:

            return("two");

        default:

            return("whatever");
    }
}

Donc, la durée de vie d'une variable locale en C/C++ est pratiquement seulement à l'intérieur de la fonction, n'est-ce pas? Ce qui signifie qu'après que char* foo(int) se termine, le pointeur qu'il retourne ne signifie plus rien, n'est-ce pas?

Je suis un peu confus quant à la durée de vie d'une variable locale. Quelle est une bonne clarification?

13 votes

La seule "var" que vous avez dans votre fonction est le paramètre int rc. Sa durée de vie se termine à chacun des return. Les pointeurs que vous renvoyez sont à des littéraux de chaîne. Les littéraux de chaîne ont une durée de stockage statique : leur durée de vie est au moins aussi longue que celle du programme.

0 votes

Que se passe-t-il si ce n'est pas une chaîne littérale mais d'autres types de littéraux, tels que : int *foo() { return &(2); // ou // int n = 2; // return &n; }

0 votes

Vérifiez la modification dans ma réponse qui répond à votre question. Notez que vous auriez dû poster cela en tant que commentaire et non en tant que réponse. Les questions, les doutes, etc. devraient être posés dans les commentaires, seules les réponses devraient être postées sous forme de réponses.

103voto

Alok Save Points 115848

Oui, la durée de vie d'une variable locale est dans la portée({,}) dans laquelle elle est créée.

Les variables locales ont un stockage automatique ou local. Automatique car elles sont automatiquement détruites une fois que la portée dans laquelle elles sont créées se termine.

Cependant, Ce que vous avez ici est une chaîne littérale, qui est allouée dans une mémoire en lecture seule définie par l'implémentation. Les chaînes littérales sont différentes des variables locales et restent en vie tout au long de la durée du programme. Elles ont une durée de vie statique [Réf 1].

Un avertissement!

Cependant, notez que toute tentative de modifier le contenu d'une chaîne littérale est un comportement indéfini (CI). Les programmes utilisateur ne sont pas autorisés à modifier le contenu d'une chaîne littérale.
Il est donc toujours conseillé d'utiliser un const lors de la déclaration d'une chaîne littérale.

const char*p = "chaine"; 

au lieu de,

char*p = "chaine";    

En fait, en C++, il est déconseillé de déclarer une chaîne littérale sans le const bien que ce ne soit pas le cas en C. Cependant, déclarer une chaîne littérale avec un const vous donne l'avantage que les compilateurs vous donneraient généralement un avertissement en cas de tentative de modifier la chaîne littérale dans le deuxième cas.

Programme d'exemple:

#include 
int main() 
{ 
    char *str1 = "chaine littérale"; 
    const char *str2 = "chaine littérale"; 
    char source[]="Chaine d'exemple"; 

    strcpy(str1,source);    // Pas d'avertissement ou d'erreur juste un Comportement Indéfini 
    strcpy(str2,source);    // Le compilateur émet un avertissement 

    return 0; 
} 

Sortie:

cc1: les avertissements sont traités comme des erreurs
prog.c: Dans la fonction 'main':
prog.c:9: erreur: le passage de l'argument 1 de 'strcpy' ignore les qualificateurs du type cible du pointeur

Remarquez que le compilateur avertit pour le deuxième cas, mais pas pour le premier.


Pour répondre à la question posée par quelques utilisateurs ici:

Qu'en est-il des littéraux intégraux?

En d'autres termes, le code suivant est-il valide?

int *foo()
{
    return &(2);
} 

La réponse est non, ce code n'est pas valide. Il est mal formé et provoquera une erreur du compilateur.

Quelque chose comme:

prog.c:3: erreur: une l-value est requise comme opérande «&» unaire

Les chaînes littérales sont des l-value, c'est-à-dire: Vous pouvez prendre l'adresse d'une chaîne littérale, mais ne pouvez pas changer son contenu.
Cependant, tout autre littéral (int, float, char, etc.) sont des r-values (le standard C utilise le terme la valeur d'une expression pour ceux-ci) et leur adresse ne peut être prise du tout.


[Réf 1]Standard C99 6.4.5/5 "Chaînes littérales - Sémantique":

Dans la phase de traduction 7, un octet ou un code de valeur zéro est ajouté à chaque séquence de caractères multioctets qui résulte d'une ou de plusieurs chaînes littérales. La séquence de caractères multioctets est ensuite utilisée pour initialiser un tableau de durée de stockage statique et de longueur juste suffisante pour contenir la séquence. Pour les littéraux de chaîne de caractères, les éléments du tableau ont le type char, et sont initialisés avec les octets individuels de la séquence de caractères multioctets; pour les littéraux de chaîne large, les éléments du tableau ont le type wchar_t, et sont initialisés avec la séquence des caractères larges...

Il n'est pas spécifié si ces tableaux sont distincts tant que leurs éléments ont les valeurs appropriées. Si le programme tente de modifier un tel tableau, le comportement est indéfini.

0 votes

Que se passe-t-il si l'utilisateur renvoie quelque chose comme ceci. char *a = &"abc"; return a; Cela ne sera-t-il pas valide?

0 votes

@Ashwin : Le type de la chaîne littérale est char (*)[4]. Cela est dû au fait que le type de "abc" est char[4] et un pointeur vers un tableau de 4 caractères est déclaré comme char (*)[4], donc si vous avez besoin de prendre son adresse, vous devez le faire comme char (*a)[4] = &"abc"; et oui, c'est valide.

0 votes

@Als "abc" est char[4]. (En raison du '\0')

81voto

Daniel Fischer Points 114146

C'est valide. Les littéraux de chaîne ont une durée de stockage statique, donc le pointeur n'est pas invalide.

Pour le langage C, cela est prescrit dans la section 6.4.5, paragraphe 6 :

En phase de traduction 7, un octet ou un code de valeur zéro est ajouté à chaque séquence de caractères multioctets qui résulte d'un ou plusieurs littéraux de chaîne. La séquence de caractères multioctets est ensuite utilisée pour initialiser un tableau de durée de stockage statique et de longueur juste suffisante pour contenir la séquence.

Et pour le langage C++ dans la section 2.14.5, paragraphes 8-11 :

8 Les littéraux de chaîne ordinaires et les littéraux de chaîne UTF-8 sont également appelés littéraux de chaîne étroite. Un littéral de chaîne étroite a le type "tableau de n const char", où n est la taille de la chaîne telle que définie ci-dessous, et a une durée de stockage statique (3.7).

9 Un littéral de chaîne qui commence par u, comme u"asdf", est un littéral de chaîne de char16_t. Un littéral de chaîne de char16_t a le type "tableau de n const char16_t", où n est la taille de la chaîne telle que définie ci-dessous ; il a une durée de stockage statique et est initialisé avec les caractères donnés. Un unique c-char peut produire plus d'un caractère char16_t sous forme de paires de remplacement.

10 Un littéral de chaîne qui commence par U, comme U"asdf", est un littéral de chaîne de char32_t. Un littéral de chaîne de char32_t a le type "tableau de n const char32_t", où n est la taille de la chaîne telle que définie ci-dessous ; il a une durée de stockage statique et est initialisé avec les caractères donnés.

11 Un littéral de chaîne qui commence par L, comme L"asdf", est un littéral de chaîne large. Un littéral de chaîne large a le type "tableau de n const wchar_t", où n est la taille de la chaîne telle que définie ci-dessous ; il a une durée de stockage statique et est initialisé avec les caractères donnés.

0 votes

FYI: cette réponse a été fusionnée à partir de stackoverflow.com/questions/16470959/…

14voto

asaelr Points 4312

Les littéraux de chaîne sont valides pour l'ensemble du programme (et ne sont pas alloués sur la pile), donc ils seront valides.

Aussi, les littéraux de chaîne sont en lecture seule, donc (pour une bonne pratique) vous devriez peut-être changer foo en const char *foo(int)

0 votes

Et si l'utilisateur retourne quelque chose comme ceci. char *a=&"abc"; return a; Est-ce que cela ne sera pas valide?

0 votes

&"abc" n'est pas char*. c'est une adresse de tableau, et son type est char(*)[4]. Cependant, return &"abc"; et char *a="abc";return a; sont valables.

0 votes

@asaelr : En fait, c'est plus que simplement pour une bonne apparence, consultez ma réponse pour plus de détails.

8voto

hyde Points 13720

Oui, c'est un code valide, voir le cas 1 ci-dessous. Vous pouvez renvoyer en toute sécurité des chaînes C à partir d'une fonction de au moins ces façons:

  • const char* à une chaîne littérale. Il ne peut pas être modifié et ne doit pas être libéré par l'appelant. Il est rarement utile dans le but de renvoyer une valeur par défaut, en raison du problème de libération décrit ci-dessous. Cela pourrait avoir du sens si vous avez vraiment besoin de passer un pointeur de fonction quelque part, donc vous avez besoin d'une fonction renvoyant une chaîne.

  • char* or const char* à un tampon char statique. Il ne doit pas être libéré par l'appelant. Il peut être modifié (soit par l'appelant s'il n'est pas const, soit par la fonction le renvoyant), mais une fonction renvoyant cela ne peut pas avoir (facilement) plusieurs tampons, donc ce n'est pas (facilement) thread-safe, et l'appelant peut avoir besoin de copier la valeur renvoyée avant d'appeler à nouveau la fonction.

  • char* à un tampon alloué avec malloc. Il peut être modifié, mais il doit généralement être explicitement libéré par l'appelant et a le surdébit d'allocation de la mémoire. strdup est de ce type.

  • const char* or char* à un tampon, qui a été passé en argument à la fonction (le pointeur renvoyé ne doit pas pointer vers le premier élément du tampon d'argument). Cela laisse la responsabilité de la gestion du tampon/mémoire à l'appelant. De nombreuses fonctions de chaînes standard sont de ce type.

Un problème est, mélanger ceux-ci dans une fonction peut devenir compliqué. L'appelant doit savoir comment gérer le pointeur renvoyé, combien de temps il est valide, et si l'appelant doit le libérer, et il n'y a aucun moyen (agréable) de le déterminer à l'exécution. Donc vous ne pouvez pas, par exemple, avoir une fonction, qui renvoie parfois un pointeur vers un tampon alloué dans le tas que l'appelant doit free, et parfois un pointeur vers une valeur par défaut d'une chaîne littérale, que l'appelant ne doit pas free.

1 votes

FYI : cette réponse a été fusionnée de stackoverflow.com/questions/16470959/…

6voto

thb Points 4370

Bonne question. En général, tu aurais raison, mais ton exemple est l'exception. Le compilateur alloue statiquement de la mémoire globale pour une chaîne littérale. Par conséquent, l'adresse retournée par ta fonction est valide.

C'est d'ailleurs une fonctionnalité assez pratique de C, n'est-ce pas? Cela permet à une fonction de renvoyer un message précomposé sans obliger le programmeur à se soucier de la mémoire dans laquelle se trouve le message.

Voir également l'observation correcte de @asaelr concernant const.

0 votes

Que se passe-t-il si l'utilisateur retourne quelque chose comme ceci. char *a=&"abc"; retourne a; Cela ne sera-t-il pas valide?

0 votes

D'accord. En fait, on peut simplement écrire const char *a = "abc";, en omettant le &. La raison en est qu'une chaîne entre guillemets résout à l'adresse de son caractère initial.

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