web-dev-qa-db-fra.com

Les variables à l'intérieur d'une boucle (while ou for) sont-elles éliminées une fois la boucle terminée?

Les variables créées dans une boucle while ou for sont-elles supprimées/supprimées de la mémoire une fois la boucle exécutée? aussi, est-ce une mauvaise habitude de coder de créer des variables temporaires dans une boucle?

dans cet exemple, crée-t-il 100 variables X puis les élimine-t-elle ou sont-elles disposées à chaque itération? Merci. Exemple:

int cc =0;
while(cc < 100){
    int X = 99; // <-- this variable
    cc++;    
}
16
Daniel Valland

Portée et durée de vie sont deux choses différentes. Pour les variables définies à la portée du bloc sans static, elles sont plus ou moins étroitement liées, mais restent des concepts distincts - et vous pouvez vous tirer une balle dans le pied si vous ne les maintenez pas droites.

Citant l'extrait de la question:

int cc =0;
while(cc < 100){
    int X = 99; // <-- this variable
    cc++;    
}

Le scope of X est la région du texte du programme dans laquelle son nom est visible. Il s'étend du point où il est défini à la fin du bloc englobant, délimité par les caractères { et }. (Le fait que le bloc fasse partie d'une instruction while n'est pas directement pertinent; c'est le bloc lui-même qui définit la portée.)

Dans le bloc, le nom X fait référence à cette variable int. En dehors du bloc, le nom X n'est pas visible.

Le durée de vie de X est le temps pendant l'exécution du programme lorsque X existe logiquement. Il commence lorsque l'exécution atteint le { d'ouverture ( avant la définition) et se termine lorsque l'exécution atteint le } de fermeture. Si le bloc est exécuté 100 fois, alors X est créé et "détruit" 100 fois et a 100 cycles de vie disjoints.

Bien que le nom X ne soit visible que dans sa portée, vous pouvez accéder (directement ou indirectement) à l ' objet appelé X à n'importe quel moment de sa vie. Par exemple, si nous passons &X à une fonction, cette fonction peut alors lire et mettre à jour l'objet, même si la fonction est complètement hors de sa portée.

Vous pouvez prendre l'adresse de X et la conserver pour l'utiliser à la fin de sa durée de vie, mais cela entraînerait un comportement indéfini. Un pointeur sur un objet dont la durée de vie est terminée est indéterminé et toute tentative de le déréférencer - ou même de faire référence à la valeur du pointeur - a un comportement indéfini.

En particulier, rien ne doit se passer lorsqu'un objet atteint la fin de sa vie. Le langage exige simplement que l'objet existe pendant son existence; en dehors de cela, tous les paris sont ouverts. L'espace de pile alloué pour contenir l'objet peut être désalloué (ce qui signifie généralement simplement déplacer le pointeur de pile) ou, en tant qu'optimisation, l'espace de pile peut rester alloué et réutilisé pour la prochaine itération de la boucle.

Aussi, est-ce une mauvaise habitude de coder de créer des variables temporaires dans une boucle?

Pas du tout. Tant que vous ne sauvegardez pas l'adresse de l'objet après la fin de sa durée de vie, il n'y a rien de mal à cela. L'allocation et la désallocation se feront très souvent à l'entrée et à la sortie de la fonction plutôt qu'au bloc, sous forme d'optimisation des performances. Limiter les variables à une portée raisonnablement étroite est une habitude très bonne codante. Cela facilite la compréhension du code en limitant les interactions inutiles entre différentes parties du code. Si X est défini à l'intérieur du corps de la boucle, rien en dehors de la boucle ne peut l'affecter (si vous n'avez pas fait quelque chose de trop compliqué), ce qui permet de raisonner plus facilement sur le code.

UPDATE: Si X était d'un type avec un constructeur et/ou un destructeur non trivial, la création et la destruction de X doivent en réalité être effectuées à l'entrée et à la sortie du bloc (à moins que le compilateur ne puisse optimiser ce code). Pour un objet de type int, ce n'est pas un problème.

19
Keith Thompson

Oui, la variable est créée et détruite N fois, à moins que le compilateur ne l’optimise (ce qu’elle peut, je crois). Ce n’est pas très grave quand vous n’avez qu’un int cependant. Cela devient plus problématique lorsque vous avez un objet complexe recréé 99 fois dans votre boucle.

Un petit exemple pratique:

#include <iostream>
using namespace std;

class TestClass
{
public:
    TestClass();
    ~TestClass();
};
TestClass::TestClass()
{
    cout<<"Created."<<endl;
}

TestClass::~TestClass()
{
    cout<<"Destroyed"<<endl;
}

int main() {
    for (int i = 0; i < 5; ++i)
    {
        TestClass c;
    }
    return 0;
}

Dans ce code, TestClass c sera recréé 5 fois. IdeOne Demo .

6
FreeNickname

Oui. Toute variable définie dans une étendue { }:

if (true) { // Forgive me father for this heresy :)
    int a = 0;
}
a = 1; // This will throw an error

Est automatiquement désalloué une fois hors de portée.

Ceci est également vrai dans ce cas:

for (int i = 0; i < 10; ++i) {
    // Do stuff
    ...
}
i = 1; // This will throw an error

La portée de i est limitée à la boucle for, même si elle n'a pas été déclarée dans une paire de { }.

4
JoErNanO

Comme indiqué précédemment, en réalité, il n'est pas nécessaire de créer/détruire une variable locale à plusieurs reprises.

C'est une perte de temps CPU inutile.

Exemple, compilé avec "gcc -S -masm = intel -fverbose-asm"

int crazyloop (int n) {
  if (n > 0) {
      int i;
      for (i = 0; i < n; i++)
         ;
  }
  return n;
}

la liste de l'Assemblée correspondante est:

_Z9crazyloopi:
.LFB0:
    .cfi_startproc
    Push    rbp #
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    mov rbp, rsp    #,
    .cfi_def_cfa_register 6
    mov DWORD PTR [rbp-20], edi # n, n
    cmp DWORD PTR [rbp-20], 0   # n,
    jle .L2 #,
    mov DWORD PTR [rbp-4], 0    # i,
.L4:
    mov eax, DWORD PTR [rbp-4]  # tmp89, i
    cmp eax, DWORD PTR [rbp-20] # tmp89, n
    jge .L2 #,
    add DWORD PTR [rbp-4], 1    # i,
    jmp .L4 #
.L2:
    mov eax, DWORD PTR [rbp-20] # D.3136, n
    pop rbp #
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc

La partie intéressante réside dans les références pour enregistrer RBP. Une fois défini, il ne change pas. La variable "i" est toujours à [rbp-4]. (Les variations du code, avec plus de vars, etc., ont donné les mêmes résultats = il n'y a pas d'allocation/désallocation répétée = modifications de la position en haut de la pile).

C'est la chose la plus sensée à faire: imaginez une boucle qui parcourt des milliards de fois.

Un autre compilateur le ferait-il différemment? Peut-être que oui, mais pourquoi ferait-il cela? 

La sécurité pourrait être une préoccupation? Peu probable; dans ce cas, le programmeur doit simplement écraser une variable avant de la laisser disparaître.

2
André Koscianski

Oui

Si vous voulez avoir accès à la variable après la boucle, vous devez la déclarer en dehors de la boucle.

while(...) {
    int i = 0;
    i++; // valid
}
i++; // invalid, i doesnt exist in this context

i en dehors de la boucle

int i = 0;
while(...) {
    i++; // valid
}
i++; // valid

la durée de vie d'une viariable est limitée au contexte {...} dans lequel elle a été créée Si on considère un objet, le destructeur serait appelé en atteignant }

1
Amxx

Les variables déclarées à l'intérieur auront leur propre portée. Vous pouvez l'utiliser quand vous savez que vous n'utiliserez pas cette variable en dehors de la portée. C'est un travail de compilation. :)

0
lappet

Oui, ils sont détruits quand ils sont hors de portée. Notez que cela n'est pas spécifique aux variables de la boucle. Cette règle s'applique à toutes les variables avec durée de stockage automatique .

0
Pradhan

Toute variable reste active dans sa portée. En dehors du champ d'application de cette variable particulière, oubliez même d'accéder à sa valeur.

for(;;)
{
    int var;  // Scope of this variable is within this for loop only.
}
// Outside this for loop variable `var` doesn't exits.
0
Shravan40