web-dev-qa-db-fra.com

En C, les accolades agissent-elles comme un cadre de pile?

Si je crée une variable dans un nouvel ensemble d'accolades, cette variable est-elle sortie de la pile sur l'accolade de fermeture, ou est-ce qu'elle se bloque jusqu'à la fin de la fonction? Par exemple:

void foo() {
   int c[100];
   {
       int d[200];
   }
   //code that takes a while
   return;
}

d prendra-t-il de la mémoire pendant le code that takes a while section?

153
Claudiu

Non, les accolades n'agissent pas comme un cadre de pile. En C, les accolades ne désignent qu'une portée de dénomination, mais rien n'est détruit et rien ne sort de la pile lorsque le contrôle en sort.

En tant que programmeur écrivant du code, vous pouvez souvent le considérer comme s'il s'agissait d'un cadre de pile. Les identifiants déclarés dans les accolades ne sont accessibles que dans les accolades, donc du point de vue du programmeur, c'est comme s'ils étaient poussés sur la pile au fur et à mesure qu'ils sont déclarés, puis sautés lorsque l'étendue est quittée. Cependant, les compilateurs n'ont pas à générer de code qui pousse/éclate quoi que ce soit à l'entrée/la sortie (et généralement, ils ne le font pas).

Notez également que les variables locales peuvent ne pas utiliser d'espace de pile du tout: elles peuvent être conservées dans les registres du processeur ou dans un autre emplacement de stockage auxiliaire, ou être entièrement optimisées.

Ainsi, le tableau d, en théorie, pourrait consommer de la mémoire pour la fonction entière. Cependant, le compilateur peut l'optimiser, ou partager sa mémoire avec d'autres variables locales dont les durées de vie d'utilisation ne se chevauchent pas.

83

Le temps pendant lequel la variable prend réellement de la mémoire est évidemment dépendant du compilateur (et de nombreux compilateurs ne règlent pas le pointeur de pile lorsque des blocs internes sont entrés et quitté dans les fonctions).

Cependant, une question étroitement liée mais peut-être plus intéressante est de savoir si le programme est autorisé à accéder à cet objet intérieur en dehors de la portée intérieure (mais dans la fonction contenante), c'est-à-dire:

void foo() {
   int c[100];
   int *p;

   {
       int d[200];
       p = d;
   }

   /* Can I access p[0] here? */

   return;
}

(En d'autres termes: le compilateur est-il autorisé à désallouer d, même si dans la pratique la plupart ne le font pas?).

La réponse est que le compilateur est autorisé à désallouer d et à accéder à p[0] où le commentaire indique un comportement indéfini (le programme n'est pas autorisé à accéder à l'objet interne en dehors de la portée interne). La partie pertinente de la norme C est 6.2.4p5:

Pour un tel objet [celui qui a une durée de stockage automatique] qui n'a pas un type de tableau de longueur variable, sa durée de vie s'étend de l'entrée dans le bloc auquel il est associé jusqu'à la fin de l'exécution de ce bloc =. (La saisie d'un bloc fermé ou l'appel d'une fonction suspend, mais ne met pas fin à l'exécution du bloc actuel.) Si le bloc est entré de manière récursive, une nouvelle instance de l'objet est créée à chaque fois. La valeur initiale de l'objet est indéterminée. Si une initialisation est spécifiée pour l'objet, elle est effectuée à chaque fois que la déclaration est atteinte lors de l'exécution du bloc; sinon, la valeur devient indéterminée chaque fois que la déclaration est atteinte.

39
caf

Votre question n'est pas suffisamment claire pour recevoir une réponse sans ambiguïté.

D'une part, les compilateurs ne font normalement aucune allocation-désallocation de mémoire locale pour les étendues de blocs imbriquées. La mémoire locale n'est normalement allouée qu'une seule fois à l'entrée de la fonction et libérée à la sortie de la fonction.

En revanche, lorsque la durée de vie d'un objet local se termine, la mémoire occupée par cet objet peut être réutilisée ultérieurement pour un autre objet local. Par exemple, dans ce code

void foo()
{
  {
    int d[100];
  }
  {
    double e[20];
  }
}

les deux tableaux occuperont généralement la même zone de mémoire, ce qui signifie que la quantité totale de stockage local nécessaire à la fonction foo est tout ce qui est nécessaire pour le le plus grand de deux tableaux, pas pour les deux eux en même temps.

Si ce dernier est qualifié de d en continuant à occuper la mémoire jusqu'à la fin de la fonction dans le contexte de votre question, c'est à vous de décider.

20
AnT

Cela dépend de l'implémentation. J'ai écrit un petit programme pour tester ce que fait gcc 4.3.4, et il alloue tout l'espace de la pile à la fois au début de la fonction. Vous pouvez examiner l'assembly produit par gcc à l'aide de l'indicateur -S.

6

Non, d [] sera pas sera sur la pile pour le reste de la routine. Mais alloca () est différent.

Edit: Kristopher Johnson (et simon et Daniel) sont à droite, et ma réponse initiale était faux. Avec gcc 4.3.4.on CYGWIN, le code:

void foo(int[]);
void bar(void);
void foobar(int); 

void foobar(int flag) {
    if (flag) {
        int big[100000000];
        foo(big);
    }
    bar();
}

donne:

_foobar:
    pushl   %ebp
    movl    %esp, %ebp
    movl    $400000008, %eax
    call    __alloca
    cmpl    $0, 8(%ebp)
    je      L2
    leal    -400000000(%ebp), %eax
    movl    %eax, (%esp)
    call    _foo
L2:
    call    _bar
    leave
    ret

Vis et apprend! Et un test rapide semble montrer qu'AndreyT a également raison sur les allocations multiples.

Ajouté beaucoup plus tard : Le test ci-dessus montre que documentation gcc n'est pas tout à fait raison. Pendant des années, il a dit (je souligne):

"L'espace pour un tableau de longueur variable est désalloué dès que le nom du tableau est portée se termine . "

3
Joseph Quinsey

Ils pourraient. Ils pourraient ne pas. La réponse dont je pense que vous avez vraiment besoin est: Ne supposez jamais rien. Les compilateurs modernes font toutes sortes d'architecture et de magie spécifique à l'implémentation. Écrivez votre code simplement et lisiblement aux humains et laissez le compilateur faire le bon travail. Si vous essayez de coder autour du compilateur, vous demandez des problèmes - et les problèmes que vous rencontrez généralement dans ces situations sont généralement horriblement subtils et difficiles à diagnostiquer.

2
user19666

Votre variable d n'est généralement pas sortie de la pile. Les accolades ne désignent pas un cadre de pile. Sinon, vous ne pourriez pas faire quelque chose comme ceci:

char var = getch();
    {
        char next_var = var + 1;
        use_variable(next_char);
    }

Si les accolades provoquaient une véritable pile/pop de pile (comme le ferait un appel de fonction), le code ci-dessus ne serait pas compilé car le code à l'intérieur des accolades ne pourrait pas accéder à la variable var qui vit en dehors des accolades (tout comme une sous-fonction ne peut pas accéder directement aux variables dans la fonction appelante). Nous savons que ce n'est pas le cas.

Les accolades sont simplement utilisées pour la définition de la portée. Le compilateur traitera tout accès à la variable "interne" depuis l'extérieur des accolades englobantes comme non valide, et il pourra réutiliser cette mémoire pour autre chose (cela dépend de l'implémentation). Cependant, il ne peut pas être extrait de la pile jusqu'à ce que la fonction englobante revienne.

Mise à jour: Voici ce que le spécification C a à dire. Concernant les objets avec une durée de stockage automatique (section 6.4.2):

Pour un objet qui n'a pas de type tableau de longueur variable, sa durée de vie s'étend de l'entrée dans le bloc auquel il est associé jusqu'à la fin de l'exécution de ce bloc.

La même section définit le terme "durée de vie" comme suit (je souligne):

La à vie d'un objet est la partie de l'exécution du programme pendant laquelle le stockage est garanti pour lui être réservé. Un objet existe, a une adresse constante et conserve sa dernière valeur stockée pendant toute sa durée de vie. Si un objet est référencé en dehors de sa durée de vie, le comportement n'est pas défini.

Le mot clé ici est, bien sûr, "garanti". Une fois que vous quittez la portée de l'ensemble interne des accolades, la durée de vie du tableau est terminée. Le stockage peut ou ne peut toujours pas lui être alloué (votre compilateur peut réutiliser l'espace pour autre chose), mais toute tentative d'accéder au tableau appelle un comportement non défini et entraîne des résultats imprévisibles.

La spécification C n'a aucune notion de trames de pile. Il ne parle que de la façon dont le programme résultant se comportera et laisse les détails de l'implémentation au compilateur (après tout, l'implémentation serait très différente sur un CPU sans pile que sur un CPU avec une pile matérielle). Il n'y a rien dans la spécification C qui indique où un cadre de pile se terminera ou non. La seule façon réelle de savoir est de compiler le code sur votre compilateur/plateforme particulier et d'examiner l'assembly résultant. L'ensemble actuel d'options d'optimisation de votre compilateur jouera probablement un rôle à cet égard également.

Si vous souhaitez vous assurer que le tableau d ne consomme plus de mémoire pendant l'exécution de votre code, vous pouvez soit convertir le code entre accolades en une fonction distincte, soit explicitement malloc et free la mémoire au lieu d'utiliser le stockage automatique.

1
bta

Il y a déjà eu beaucoup d'informations sur la norme indiquant qu'elle est en effet spécifique à l'implémentation.

Ainsi, une expérience pourrait être intéressante. Si nous essayons le code suivant:

#include <stdio.h>
int main() {
    int* x;
    int* y;
    {
        int a;
        x = &a;
        printf("%p\n", (void*) x);
    }
    {
        int b;
        y = &b;
        printf("%p\n", (void*) y);
    }
}

En utilisant gcc on obtient ici deux fois la même adresse: Coliro

Mais si nous essayons le code suivant:

#include <stdio.h>
int main() {
    int* x;
    int* y;
    {
        int a;
        x = &a;
    }
    {
        int b;
        y = &b;
    }
    printf("%p\n", (void*) x);
    printf("%p\n", (void*) y);
}

En utilisant gcc on obtient ici deux adresses différentes: Coliro

Donc, vous ne pouvez pas être vraiment sûr de ce qui se passe.

0
Mi-He

Je crois que cela sort du cadre, mais n'est pas sorti de la pile jusqu'à ce que la fonction revienne. Il occupera donc toujours de la mémoire sur la pile jusqu'à ce que la fonction soit terminée, mais non accessible en aval de la première accolade fermante.

0
simon