1
votes

Comment pouvons-nous accéder aux variables automatiques et statiques en dehors de leur portée en C?

Les variables automatiques et statiques ont une portée limitée au bloc dans lequel elles sont définies. Puisque les variables Auto sont définies dans la pile, si la fonction se ferme, la pile est détruite et la mémoire de la variable automatique est libérée. Mais j'ai lu quelque part "Cependant, ils peuvent également être consultés en dehors de leur portée en utilisant le concept de pointeurs donné ici en pointant vers l'emplacement mémoire très exact où résident les variables." Est-ce correct?

De plus, les variables statiques sont définies dans la section data afin qu'elle conserve son existence jusqu'à la fin du programme. La portée se trouve dans le bloc dans lequel elle est définie. Y a-t-il un moyen par lequel nous pouvons accéder à une variable statique à partir de n'importe quelle autre fonction? De plus, existe-t-il un moyen d'accéder à des variables statiques à partir de n'importe quel autre fichier?


5 commentaires

pouvez-vous donner un exemple de variable automatique à laquelle vous souhaitez accéder en dehors de la fonction dans laquelle elle a été définie?


Et pourquoi en avez-vous besoin? Il existe peut-être un meilleur moyen de réaliser tout ce que vous essayez de faire


La portée et la durée de vie sont des choses différentes. Vous pouvez utiliser des pointeurs pour accéder aux objets en dehors de leur portée, mais pas en dehors de leur durée de vie.


J'ai juste appris que je supposais que vous n'êtes pas intéressé par le cas des variables accessibles à partir de fonctions appelées depuis le périmètre et pendant la durée de vie. Pouvez-vous clarifier?


@Akib: Vous avez accepté une mauvaise réponse qui ne traite pas de la manière dont l'adresse d'un objet peut être transmise à d'autres fonctions pendant sa durée de vie. La réponse de rici est meilleure.


3 Réponses :


0
votes

Notez, comme les commentaires le soulignent à juste titre, je fais une hypothèse ici, l'hypothèse que le cas le plus simple d'appeler une autre fonction n'est pas de quoi il s'agit. Cette hypothèse n'a pas (encore) été confirmée ou rejetée par OP. Ce cas est discuté par ex. dans la réponse de rici.

L'existence de variables automatiques n'existe pas seulement pour "dans" leur portée (simplifié: seul le code entre le même {} englobant peut utiliser leur identifiant), elles sont également restreintes à "pendant" leur "portée chronologique" c'est-à-dire leur durée de vie (simplifiée après le démarrage de l'exécution du code dans la fonction et la fin de son exécution). Il est possible d'accéder à l'emplacement mémoire d'une variable via un pointeur, qui a été mis à leur adresse (ce qui n'est possible que dans leur périmètre, car l'accès via leur identifiant est nécessaire) tant que cela se fait pendant leur durée de vie, oui.

Mais comment trouver ce pointeur de n'importe où ailleurs?
Peut-être en étant écrit (de l'intérieur de leur portée et pendant leur durée de vie) dans une variable globale.

Mais quel "autre" code devrait alors utiliser cette valeur? (rappelez-vous que je mets l'appel des fonctions à côté ici)
Cela nécessite le multithreading / le multitâche / le multitâche. Disons qu'une routine de service d'interruption le fait. Il devrait voir le même espace d'adressage que la portée des variables, c'est-à-dire qu'aucune unité de gestion de mémoire ne gêne la magie de la mémoire virtuelle. Ce n’est pas le cas pour de nombreuses implémentations multifactorielles, mais il est vrai que pour quelques-unes d’entre elles, continuons donc.

Cet ISR imaginé devrait s'assurer qu'il n'accède à la variable auto que pendant qu'elle existe réellement (c'est-à-dire pendant sa durée de vie), sinon il accèderait à peu près à ce qui est effectivement un emplacement de mémoire aléatoire sans signification. Et cela suppose que l'ISR est réellement autorisé / capable d'accéder à cette mémoire. Même sans MMU, il existe des implémentations qui peuvent / auront des exceptions.
Cela introduit le besoin de mécanismes de synchronisation, par ex. sémaphores.

Donc, dans certains environnements, ce serait possible, mais totalement inutile (les variables globales sont toujours impliquées), coûteux, difficile à comprendre et presque impossible à porter. (rappelez-vous que je mets l'appel d'une fonction de côté ici)

Similaire pour les variables statiques.

Dans le cas des variables statiques locales de fonction, elles existeraient au moins de manière fiable, mais y accéder nécessiterait toujours que la valeur du pointeur soit transportée hors de leur portée. Pour les variables statiques qui pourraient effectivement être effectuées via la valeur de retour de la fonction comme démontré dans la réponse de yashC.

Dans le cas de variables "statiques" comprises comme des variables restreintes à la portée du fichier, le pointeur devra quand même être transporté hors de la portée du fichier.
Cela ne ferait que vaincre ce qui est probablement l'intérêt d'une variable restreinte de portée de fichier. Mais je pourrais imaginer une sorte de schéma de privilèges d'accès, comme dans "Voici la clé du coffre-fort. Manipulez-le avec précaution."

Comme mentionné au début de cette réponse, je mets de côté l'appel d'autres fonctions. Oui, le moyen le plus simple de quitter la portée d'une fonction est d'en appeler une autre. Si cette autre fonction a un paramètre de pointeur, elle peut l'utiliser pour accéder en lecture et en écriture à la variable automatique de la fonction appelante. C'est le cas normal des paramètres d'appel par référence pris en charge par C.
L'appel d'une fonction fournit également un autre moyen encore plus simple d'accéder en lecture à la valeur d'une variable automatique de la fonction appelante, mais sans accéder en écriture et en n'accédant pas réellement à la variable automatique elle-même, en utilisant uniquement sa valeur. De cette façon, c'est le mécanisme trivial d'un paramètre d'appel par valeur, il ne nécessite même pas de pointeur. Les deux méthodes (paramètre d'appel par référence et paramètre d'appel par valeur) garantissent de manière pratique que la valeur ne change pas pendant l'exécution de la fonction appelée. (cette fois, je mets de côté le cas multi-thread, car cela est discuté dans la partie principale de cette réponse).


9 commentaires

Il ne nécessite pas de multithreading; il existe un cas beaucoup plus simple. Il vous suffit de transmettre l'adresse d'une variable locale à une fonction appelée. Cela peut être n'importe quelle fonction qui prend une chaîne C (ou un tableau d'ailleurs). Ou une fonction qui renvoie des valeurs supplémentaires par appel par référence.


@rici Je considérerais que le code appelé à partir de la portée est dans la portée, au moins pour l'idée de cette question. Au moins, je pense que la question est conçue comme ça. Bonne contribution cependant. J'essaierai de clarifier mon hypothèse.


Ce n'est pas ce que signifie la portée. La portée n'est pas la même que la durée de vie.


@rici Oui je sais et c'est exactement ce que j'élabore dans cette réponse.


Re «L'existence de variables automatiques n'est pas seulement d'exister« dans »leur champ d'application»: c'est incorrect; l'existence d'objets à durée de stockage automatique ne se limite pas à leur portée. Considérons un objet O, son identifiant I et un code C dans lequel I n'est pas dans la portée. Il est possible que O existe et soit accessible par C. Ceci est démontré dans la réponse de rici .


Re "Cela nécessite le multithreading / multitâche / multi-tâches": Ceci est faux, encore une fois comme démontré dans réponse de rici , dans laquelle un tel object est utilisé sans aucun multithreading / multitâche / multitâche.


Re «Donc, dans certains environnements, ce serait possible, mais complètement inutile (les variables globales sont toujours impliquées), coûteux, difficile à comprendre et presque impossible à porter»: C'est faux, encore une fois comme démontré précédemment. Il est utile, peu coûteux, facile à comprendre et facile à porter. De plus, c'est courant; de nombreuses routines de bibliothèque standard C ont des paramètres qui sont des pointeurs, et ils peuvent être passés des pointeurs vers des objets avec une durée de stockage automatique.


@EricPostpischil Merci pour votre contribution. J'ai souligné mon hypothèse, qui n'a malheureusement pas été confirmée ou rejetée par OP. Oui, vous avez raison, sans cette hypothèse, les déclarations sont déroutantes au point d'être inexactes. J'ai simplement senti que le cas simple est si simple qu'il ne peut pas être au cœur de la question des PO.


@rici Merci pour votre contribution, oui, décrire la durée de vie comme "pendant" la portée, sans mentionner le terme correct a réduit la clarté de ma réponse. Voir également le commentaire ci-dessus sur le cas plus simple de l'appel d'une fonction. J'ai incorporé les deux dans ma réponse.



2
votes

Comme vous l'avez dit, les variables statiques existent tout au long du cycle de vie du programme, c'est-à-dire que la mémoire qui leur est allouée n'est pas détruite tant que le programme est en cours d'exécution. Ainsi, pour accéder à une telle variable en dehors de sa portée, nous pouvons faire passer le pointeur vers cet emplacement mémoire via un pointeur. Un petit exemple pour montrer le même

#include <stdio.h>
#include <stdlib.h>

int* func()
{
        static int a = 0;
        a++;
        printf("a in func = %d\n", a);
        return &a;
}

int main()
{
        int *p;
        p = func();
        printf("a in main from ptr : %d\n", *p);
        *p++;
        p = func();
        return 0;
}

Comme vous pouvez le voir dans l'exemple, func () renvoie le pointeur vers la variable statique qu'il a déclarée, et n'importe laquelle qui souhaite accéder à la variable a , peut utiliser ce pointeur. REMARQUE: nous ne pouvons le faire que parce que la vie de la variable statique se déroule tout au long du programme. Indépendamment du fait que la variable statique se trouve dans une fonction différente ou dans un fichier différent, tant que vous pouvez savoir comment obtenir le pointeur vers cette variable statique, vous pouvez l'utiliser.

Maintenant, voici le cas de la variable auto.

Que se passe-t-il si vous exécutez le programme ci-dessus en changeant a de static en auto ? vous verrez que lors de la compilation d'un avertissement warning: la fonction retourne l'adresse de la variable locale [-Wreturn-local-addr] est lancée et lors de l'exécution, nous obtenons une erreur de segmentation . La cause en est que la variable auto n'existe que dans sa portée, c'est-à-dire que tant que la fonction func () est exécutée, la variable a a de la mémoire allouée pour elle-même. Dès que la fonction se termine, la mémoire allouée à la variable a est libérée et ainsi la valeur pointée par le pointeur p se trouve à un emplacement mémoire non alloué (résultant en une erreur de segmentation) .


12 commentaires

À propos de la chose segfault: vous souhaitez! Sur presque toutes les implémentations, la mémoire de la pile est en fait toujours allouée comme un seul gros morceau (sauf l'expansion automatique via les pages de garde), et l'allocation / la désallocation de la pile ne fait que déplacer le pointeur de haut de la pile. Pour cette raison, même si vous lisez / écrivez au-dessus du sommet actuel de la pile, aucune violation d'accès n'est déclenchée - vous avez simplement écrit dans un emplacement logiquement non alloué, mais "physiquement" valide, donc soit vous écrasez une variable locale non liée, soit vous écrivez simplement sur des données inutilisées.


Là encore, si votre implémentation écrit des valeurs Canary sur des parties inutilisées de la pile ou effectue des vérifications supplémentaires à chaque accès mémoire (pensez à AddressSanitizer builds), elle peut détecter le problème; mais dans une construction optimisée, généralement l'utilisation hors de portée est soit silencieuse, soit entraîne une corruption silencieuse.


@MatteoItalia Oui, vous avez raison de souligner que la mémoire existe mais qu'elle est logiquement hors de portée. mais par souci de simplicité, nous pouvons l'appeler ici un emplacement mémoire inexistant?


Mon point n'était pas beaucoup de termes, mais d'inexactitudes factuelles. Vous parlez d'obtenir une erreur de segmentation lors de l'écriture sur un pointeur faisant référence à un emplacement de pile désormais non alloué de manière logique; ce n'est pas ce qui se passe généralement, car les segfaults sont le produit d'une protection de la mémoire basée sur le matériel, alors qu'ici, en ce qui concerne le matériel, le pointeur pointe toujours vers une mémoire valide.


Cela étant dit, je ne parlerais pas d'emplacements mémoire inexistants, mais simplement de mémoire désallouée, qui est une construction entièrement logique, qui n'implique rien de particulier sur l'état réel d'une telle mémoire.


@MatteoItalia eh bien, quand j'ai exécuté le programme, j'ai eu un défaut de segmentation. Il s'agit de la sortie a in func = 1 Segmentation fault (core dumped)


Si vous l'exécutez sous gdb, vous verrez qu'il déréférence un pointeur NULL. C'est juste une faveur que gcc vous fait - il détecte le problème au moment de la compilation (en fait, il affiche un avertissement à ce sujet), et le transforme essentiellement en un return NULL en aide au débogage (il est permis de changer quoi que ce soit parce que vous déréférencer un pointeur non valide, ce qui est un comportement non défini, donc tout est autorisé) Essayez de passer l'adresse du local hors de la fonction par des moyens plus compliqués (par exemple, une valeur globale volatile ) et vous verrez que tout semble fonctionner parfaitement.


@MatteoItalia +1 TIL


La question ne pose pas de question sur l'accès à un objet après la fin de sa durée de vie. Il demande comment accéder à un objet à partir d'un code dans lequel l'identifiant de l'objet n'est pas dans la portée. Ceci est correct et se produit fréquemment chaque fois que l’adresse d’un objet est transmise à une autre routine.


@EricPostpischil Exemple de programme adresse la méthode d'accès aux variables hors de la portée. Comme la durée de vie et la portée sont mentionnées dans la question ainsi que comment y accéder, ne vaudra-t-il pas mieux apporter des éclaircissements sur ce point également?


@yashC: La question ne pose pas de question sur l'accès à un objet après la fin de sa durée de vie. Dans cette réponse, il est montré qu'un objet avec une durée de stockage automatique ne doit pas être accédé, comme par l'exemple de programme, après sa durée de vie. Mais ce n'est pas ce sur quoi porte la question. Au lieu de montrer un exemple d'une situation qui n'a pas été interrogée pour un comportement que l'on ne peut pas utiliser correctement, la réponse devrait montrer un exemple qui a été posé à ce sujet on peut utiliser correctement. Le fait est qu'il est parfaitement convenable d'accéder à un objet attribué automatiquement par adresse pendant sa durée de vie ...


… Et donc la réponse devrait le montrer. L'exemple de programme doit être celui qui prend l'adresse d'un objet avec une durée de stockage automatique et passe cette adresse à un sous-programme, qui accède à cet objet pendant sa durée de vie mais alors que son identifiant n'est pas dans la portée. Telle qu'elle est écrite, cette réponse décrit une incapacité à accéder à un objet alloué automatiquement via un pointeur, mais c'est parce qu'elle a configuré l'exemple en cas d'échec en essayant après la fin de la durée de vie ce qui n'est pas ce que la question a posée. Il doit mettre en place une situation qui fonctionne c'est ce que la question a posée.



3
votes

Voici un exemple très simple:

void print_msg(const char* msg) {
  printf("The message is: %s\n", msg);
}

int main(void) {
  char m[] = "Hello, world!";
  print_msg(m);
}

Ici, m est une variable automatique, qui n'est pas dans la portée de print_msg . Mais print_msg a clairement accès à sa valeur.

Ne confondez pas "portée" avec "durée de vie". La portée d'une variable est la partie du programme où le nom de la variable est visible (et peut donc être utilisé). La durée de vie d'une valeur est la période pendant l'exécution du programme pendant laquelle une valeur existe. La portée concerne le texte du programme; il concerne la compilation. La durée de vie concerne l'exécution du programme.


0 commentaires