web-dev-qa-db-fra.com

"durée de vie" du littéral de chaîne en C

Le pointeur renvoyé 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");
    }
}

Ainsi, la durée de vie d'une variable locale en C/C++ est pratiquement uniquement dans la fonction, non? Ce qui signifie, après la fin de char* foo(int), que le pointeur qu'il renvoie ne signifie plus rien?

Je suis un peu confus quant à la durée de vie de la var locale. Quelqu'un pourrait-il me donner une bonne clarification?

79
user113454

Oui, la durée de vie d'une variable locale est dans la portée ({, }) dans lequel il est créé.
Les variables locales ont un stockage automatique ou local.
Automatique car ils sont automatiquement détruits une fois l'étendue dans laquelle ils sont créés terminée.

Cependant, ce que vous avez ici est un littéral de chaîne, qui est alloué dans une mémoire morte définie par implémentation. Les littéraux de chaîne sont différents des variables locales et ils restent actifs tout au long de la durée de vie du programme. Ils ont durée statique [Réf 1] durée de vie.

Un mot d'avertissement!
Cependant, notez que toute tentative de modification du contenu d'un littéral de chaîne est un comportement indéfini. Les programmes utilisateur ne sont pas autorisés à modifier le contenu d'un littéral de chaîne.
Par conséquent, il est toujours recommandé d'utiliser un const lors de la déclaration d'un littéral de chaîne.

const char*p = "string"; 

au lieu de,

char*p = "string";    

En fait, en C++, il est déconseillé de déclarer un littéral de chaîne sans const mais pas en c. Cependant, déclarer un littéral de chaîne avec un const vous donne l'avantage que les compilateurs vous donnent généralement un avertissement au cas où vous tenteriez de modifier le littéral de chaîne dans le deuxième cas.

Exemple de programme :

#include<string.h> 
int main() 
{ 
    char *str1 = "string Literal"; 
    const char *str2 = "string Literal"; 
    char source[]="Sample string"; 

    strcpy(str1,source);    //No warning or error just Uundefined Behavior 
    strcpy(str2,source);    //Compiler issues a warning 

    return 0; 
} 

Sortie:

cc1: les avertissements sont traités comme des erreurs
prog.c: Dans la fonction "principale":
prog.c: 9: erreur: la transmission de l'argument 1 de "strcpy" élimine les qualificatifs du type cible du pointeur

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


EDIT: Pour répondre au Q demandé par quelques utilisateurs ici:

Quel est le problème avec les littéraux intégraux?
En d'autres termes, ce code est-il valide:

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

La réponse est non, ce code n'est pas valide, il est mal formé et donnera une erreur de compilation.
Quelque chose comme:

prog.c:3: error: lvalue required as unary ‘&’ operand

Les littéraux de chaîne sont des valeurs l, c'est-à-dire: vous pouvez prendre l'adresse d'un littéral de chaîne mais ne pouvez pas changer son contenu.
Cependant, tous les autres littéraux (int, float, char etc.) sont des valeurs r (la norme c utilise le terme la valeur de une expression pour ceux-ci) & leur adresse ne peut pas être prise du tout.


[Réf 1] C99 standard 6.4.5/5 "String Literals - Semantics":

Dans la phase de traduction 7, un octet ou un code de valeur zéro est ajouté à chaque séquence de caractères multi-octets qui résulte d'un ou plusieurs littéraux de chaîne. La séquence de caractères multi-octets est ensuite utilisée pour initialiser un tableau de durée et de longueur de stockage statique juste suffisant 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 multi-octets; 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 de caractères larges ...

Il n'est pas précisé si ces tableaux sont distincts à condition que leurs éléments aient les valeurs appropriées. Si le programme tente de modifier un tel tableau, le comportement n'est pas défini .

81
Alok Save

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

Pour C, cela est obligatoire dans la section 6.4.5, paragraphe 6:

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

Et pour 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 définie ci-dessous et a une durée de stockage statique (3.7).

9 Littéral de chaîne commençant par u, tel que u"asdf", est un char16_t chaîne littérale. UNE char16_t le littéral de chaîne 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 seul caractère C peut produire plusieurs char16_t caractère sous forme de paires de substitution.

10 Littéral de chaîne commençant par U, tel que U"asdf", est un char32_t chaîne littérale. UNE char32_t le littéral de chaîne 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 Littéral de chaîne commençant par L, tel que 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.

75
Daniel Fischer

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

De plus, les littéraux de chaîne sont en lecture seule, donc (pour un bon style), vous devriez peut-être changer foo en const char *foo(int)

14
asaelr

Oui, c'est un code valide, cas 1 ci-dessous. Vous pouvez retourner en toute sécurité les chaînes C d'une fonction au moins de ces manières:

  • const char* à un littéral de chaîne. Ne peut pas être modifié, ne doit pas être libéré par l'appelant. Rarement utile pour 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 devez réellement passer un pointeur de fonction quelque part, vous avez donc besoin d'une fonction renvoyant une chaîne.

  • char* ou const char* au tampon de caractères statiques. Ne doit pas être libéré par l'appelant. Peut être modifié (soit par l'appelant sinon const, soit par la fonction qui le renvoie), mais une fonction renvoyant ceci ne peut pas (facilement) avoir plusieurs tampons, donc pas (facilement) sûr pour les threads, et l'appelant peut avoir besoin de copier le retour avant d'appeler à nouveau la fonction.

  • char* à un tampon alloué avec malloc. Peut être modifié, mais doit généralement être explicitement libéré par l'appelant et a la surcharge d'allocation de tas. strdup est de ce type.

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

Un problème est que les mélanger dans une seule fonction peut devenir compliqué. L'appelant doit savoir comment il doit gérer le pointeur renvoyé, combien de temps il est valide et si l'appelant doit le libérer, et il n'y a pas (Nice) de moyen de le déterminer lors de l'exécution. Ainsi, vous ne pouvez pas, par exemple, avoir une fonction, qui renvoie parfois un pointeur vers un tampon alloué par segment dont l'appelant a besoin pour free, et parfois un pointeur vers une valeur par défaut à partir du littéral de chaîne, lequel appelant doit - pasfree.

7
hyde

Bonne question. En général, vous auriez raison, mais votre exemple est l'exception. Le compilateur alloue statiquement de la mémoire globale pour un littéral de chaîne. Par conséquent, l'adresse renvoyée par votre fonction est valide.

Que ce soit ainsi est une caractéristique plutôt pratique de C, n'est-ce pas? Il permet à une fonction de renvoyer un message précomposé sans forcer le programmeur à se soucier de la mémoire dans laquelle le message est stocké.

Voir aussi l'observation correcte de @ asaelr concernant _ const.

6
thb

Les variables locales ne sont valides que dans la portée où elles sont déclarées, mais vous ne déclarez aucune variable locale dans cette fonction.

Il est parfaitement valide de renvoyer un pointeur sur un littéral de chaîne à partir d'une fonction, car un littéral de chaîne existe tout au long de l'exécution du programme, tout comme le ferait un static ou une variable globale.

Si vous vous inquiétez de ce que vous faites peut être invalide non défini, vous devez activer vos avertissements du compilateur pour voir s'il y a quelque chose que vous faites mal.

3
AusCBloke

str ne sera jamais un pointeur suspendu. Because it points to static address où résident les littéraux de chaîne. Il sera principalement readonly et global dans le programme lorsqu'il sera chargé. Même si vous essayez de libérer ou de modifier, cela lancera segmentation faultsur les plates-formes avec protection de la mémoire.

2
qwr

Une variable locale est allouée sur la pile. Une fois la fonction terminée, la variable sort du cadre et n'est plus accessible dans le code. Cependant, si vous avez un pointeur global (ou tout simplement - pas encore hors de portée) que vous avez affecté pour pointer vers cette variable, il pointera vers l'endroit de la pile où se trouvait cette variable. Il peut s'agir d'une valeur utilisée par une autre fonction ou d'une valeur vide de sens.

0
Imp

Dans l'exemple ci-dessus que vous avez montré, vous retournez en fait les pointeurs alloués à la fonction qui appelle ce qui précède. Il ne deviendrait donc pas un pointeur local. Et en plus des pointeurs qui doivent être retournés, la mémoire est allouée en segment global.

Te remercie,

Viharri P L V.

0
VIHARRI PLV