«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"); } } 

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

Je suis un peu confus au sujet de la durée de vie de la var locale. Quelqu’un pourrait-il me donner une bonne précision?

Oui, la durée de vie d’une variable locale est comprise dans l’étendue ( { , } ) dans laquelle elle est créée.
Les variables locales ont un stockage automatique ou local.
Automatique car ils sont automatiquement détruits une fois la scope de leur création terminée.

Cependant, ce que vous avez ici est un littéral de chaîne, qui est alloué dans une mémoire en lecture seule définie par l’implémentation. Les littéraux de chaîne sont différents des variables locales et restnt actifs pendant toute la durée de vie du programme. Ils ont une durée de vie statique [Ref 1] .

Un mot d’avertissement!
Toutefois, notez que toute tentative de modification du contenu d’un littéral de chaîne est un comportement non dé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 tout en déclarant un littéral de chaîne.

 const char*p = "ssortingng"; 

au lieu de,

 char*p = "ssortingng"; 

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

Exemple de programme :

 #include int main() { char *str1 = "ssortingng Literal"; const char *str2 = "ssortingng Literal"; char source[]="Sample ssortingng"; strcpy(str1,source); //No warning or error just Uundefined Behavior strcpy(str2,source); //Comstackr issues a warning return 0; } 

Sortie:

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

Notez que le compilateur avertit pour le second cas mais pas pour le premier.


EDIT: Pour répondre au Q demandé par deux 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 vous ne pouvez pas modifier son contenu.
Cependant, tous les autres littéraux ( int , float , char etc.) sont des valeurs r (c standard utilise le terme comme valeur d’une expression ) et leur adresse ne peut pas être prise du tout.


[Ref 1] C99 standard 6.4.5 / 5 “Ssortingng Literals – 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 multi-octets résultant d’un littéral de chaîne ou de littéraux. La séquence de caractères multi-octets 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 multi-octets; pour les littéraux de chaînes larges, les éléments de tableau ont le type wchar_t et sont initialisés avec la séquence de caractères larges …

Il n’est pas spécifié 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 est indéfini .

C’est valide, les littéraux de chaîne ont une durée de stockage statique, de sorte que le pointeur n’est pas en attente.

Pour C, cela est prévu à 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 résultant d’un littéral de chaîne ou de littéraux. La séquence de caractères multi-octets 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 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 étroits. Une chaîne littérale étroite a le type «array of 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 Un littéral de chaîne commençant par u, tel que u"asdf" , est un littéral de chaîne char16_t . Un littéral de chaîne char16_t a le type «array of 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 simple caractère c peut produire plus d’un caractère char16_t sous la forme de paires de substitution.

10 Un littéral de chaîne commençant par U, tel que U"asdf" , est un littéral de chaîne char32_t . Un littéral de chaîne char32_t a le type «array of n const char32_t », où n est la taille de la chaîne 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 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 «array of n const wchar_t », où n est la taille de la chaîne définie ci-dessous; il a une durée de stockage statique et est initialisé avec les caractères donnés.

Les littéraux de chaîne sont valables pour tout le programme (et ne sont pas affectés à la stack), ils seront donc valides.

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)

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

C’est une caractéristique plutôt pratique de C, non? Il permet à une fonction de retourner 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

Oui, c’est un code valide, cas 1 ci-dessous. Vous pouvez renvoyer en toute sécurité des chaînes C d’une fonction au moins de la manière suivante:

  • const char* à un littéral de chaîne. Ne peut pas être modifié, ne doit pas être libéré par l’appelant. Rarement utile dans le but de retourner 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 réellement besoin de 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 un appelant si pas const, soit par la fonction qui le retourne), mais une fonction qui renvoie ceci ne peut pas (facilement) avoir plusieurs tampons, donc pas (facilement) thread-safe et l’appelant peut avoir besoin de copier le valeur 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 doit avoir une surcharge de segment de mémoire. strdup est de ce type.

  • const char* ou char* à un tampon, qui a été passé en argument à la fonction (le pointeur retourné n’a pas besoin de pointer vers le premier élément du tampon d’argument). 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 le mélange de celles-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 pas de moyen (agréable) de le déterminer lors de l’exécution. Donc, vous ne pouvez pas par exemple avoir une fonction, qui renvoie parfois un pointeur sur un tampon alloué au tas que l’appelant doit free , et parfois un pointeur sur une valeur par défaut du littéral de chaîne, appelant qui ne doit pas être free .

Les variables locales ne sont valides que dans la scope déclarée, mais vous ne déclarez aucune variable locale dans cette fonction.

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

Si vous craignez que ce que vous faites ne soit pas indéfini, vous devez activer les avertissements du compilateur pour voir si quelque chose ne va pas.

str ne sera jamais un pointeur. Because it points to static address où résident les littéraux de chaîne. Il sera principalement en readonly et global au programme quand il sera chargé. Même si vous essayez de libérer ou de modifier, cela entraînera segmentation fault sur les plates-formes avec protection de la mémoire .

Une variable locale est allouée sur la stack. Une fois la fonction terminée, la variable est hors de scope et n’est plus accessible dans le code. Cependant, si vous avez un pointeur global (ou simplement pas encore hors de scope) que vous avez assigné pour pointer vers cette variable, il pointe vers l’endroit de la stack où se trouvait cette variable. Cela pourrait être une valeur utilisée par une autre fonction ou une valeur sans signification.

Dans l’exemple ci-dessus que vous avez montré, vous retournez réellement les pointeurs alloués à la fonction qui appelle ce qui précède. Donc, cela ne deviendrait pas un pointeur local. De plus, les pointeurs à renvoyer, la mémoire est allouée dans le segment global.

En vous remerciant

Viharri PL V.