9
votes

C ++ Pile et portée

J'ai essayé ce code sur Visual C ++ 2008 et cela montre que A et B n'ont pas la même adresse.

int main()
{
    {
        int A;
        printf("%p\n", &A);
    }

    int B;
    printf("%p\n", &B);
}


3 commentaires

Compilez-vous une version de version ou une construction de débogage?


Ce que vous suggérez est une optimisation espace , mais pas nécessairement une optimisation de la vitesse.


Si tous les habitants sont trop gros pour s'adapter à une ligne de cache, cela devient une optimisation de vitesse puisque vous n'avez pas de cache Miss.


7 Réponses :


1
votes

C'est probablement que le compilateur met sur le même cadre de pile. Donc, même si A n'est pas accessible à l'extérieur de sa portée, le compilateur est libre de le lier à un endroit en mémoire tant que cela ne corrompre pas la sémantique du code. En bref, ils obtiennent les deux sur la pile en même temps que vous exécutez votre principal.


2 commentaires

Savoir aussi: N'essayez pas d'être plus intelligent que votre compilateur. (Sans vouloir vous offenser)


Je sais que le compilateur est autorisé à le faire, mais je me demande toujours pourquoi il le fait :)



8
votes

Réutiliser un espace de pile pour les locaux comme celui-ci est une optimisation très courante. En fait, sur une construction optimisée, si vous n'avez pas pris l'adresse des locaux, le compilateur pourrait même pas allouer de l'espace de pile et la variable ne vivrait que dans un registre.

Vous ne voyez peut-être pas que cette optimisation se produit pour plusieurs raisons.

Premièrement, si les optimisations sont éteintes (comme une construction de débogage), le compilateur ne fera que de ne pas faire de débogage, vous pouvez afficher la valeur d'un même s'il n'est plus utilisé dans la fonction.

Si vous compilez avec des optimisations, je suppose que vous recevez depuis que vous prenez l'adresse de la section locale et la transmettant à une autre fonction, le compilateur ne veut pas réutiliser le magasin puisqu'il n'est pas clair ce que cette fonction fait avec l'adresse.

On peut également imaginer un compilateur qui n'utiliserait pas cette optimisation à moins que l'espace de pile utilisé par une fonction dépasse certains seuils. Je ne connais pas de compilateurs qui le font, car la réutilisation de la réutilisation de l'espace de variables locales qui ne sont plus utilisées n'a plus de coût de zéro et pourrait être appliquée à travers le tableau.

Si la croissance de la pile est une préoccupation sérieuse pour votre application, c'est-à-dire dans certains scénarios, vous frappez des débordements de pile, vous ne devez pas compter sur l'optimisation de l'espace de pile du compilateur. Vous devriez envisager de déplacer de gros tampons sur la pile vers le tas et de travailler pour éliminer la récursion très profonde. Par exemple, sur les threads Windows ont une pile de 1 Mo par défaut. Si vous craignez de déborder que, car vous allouiez 1k de mémoire sur chaque cadre de pile et que vous allez 1000 appels récursifs profonds, le correctif n'est pas d'essayer de coaxer le compilateur pour économiser de l'espace de chaque image de pile.


5 commentaires

Je ne pense pas que le compilateur est autorisé à mettre A et B dans des registres de l'exemple ci-dessus, car & et & B sont nécessaires.


@Arak - Droite, c'est pourquoi j'ai dit "si vous n'avez pas pris l'adresse".


@Michael, désolé je n'ai pas vu ça :)


Il y a des avantages pour ne pas réutiliser l'adresse. Il fabrique plus facilement les commandes d'instructions pour les transformateurs qui le soutiennent.


@Patros - TRUE - L'optimisation de la vitesse par rapport à la taille pourrait introduire un code généré différent ici.



1
votes

A est alloué sur la pile après B. B est déclaré après A dans le code (qui n'est pas autorisé par C90 à la manière), mais il est toujours dans la portée supérieure de la fonction principale et existe donc depuis le début de la fin de la fin. Donc, B est poussé lorsque le démarrage principal, A est poussé lorsque la portée intérieure est entrée et apparaissait lorsqu'elle est laissée, puis B est apparu lorsque la fonction principale est laissée.


2 commentaires

Le compilateur est libre d'allouer A et B dans n'importe quel ordre sur la pile si longtemps que les constructeurs / destructeurs sont exécutés dans le bon ordre et l'emplacement.


Ils n'ont même pas besoin d'être sur la pile. Si l'adresse n'a pas été prise dans l'exemple ci-dessus, le compilateur pourrait simplement les laisser dans des registres et la fonction pourrait prendre zéro espace de pile.



3
votes

Pourquoi ne pas vérifier l'assemblage?

J'ai changé votre code légèrement pour que INT A = 1; et int b = 2; pour le rendre légèrement plus facile à déchiffrer.

de g ++ avec des paramètres par défaut: xxx

En fin de compte, on dirait que le compilateur n'a tout simplement pas pris la peine de les mettre dans la même adresse. Il n'y avait pas d'optimisation de lunettes de lunette de fantaisie impliquée. Soit il n'essayait pas d'optimiser, soit il a décidé qu'il n'y avait aucun avantage.

AVIS A est attribué, puis imprimé. Ensuite, B est attribué et imprimé, comme dans la source d'origine. Bien sûr, si vous utilisez différents paramètres du compilateur, cela pourrait sembler complètement différent.


0 commentaires

2
votes

Par mes connaissances, l'espace de B est réservé à l'entrée à l'entrée principale, et non à la ligne xxx

Si vous cassez le débogueur avant cette ligne, vous êtes néanmoins capable d'obtenir L'adresse de B. Le Stackpointer ne change pas non plus après cette ligne. La seule chose qui se passe à cette ligne est que le constructeur de B est appelé.


1 commentaires

Il n'y a rien dans la norme C ++ qui interdit ou le permet. Donc, il est tout à fait possible que votre compilateur alloue en fait la mémoire de B immédiatement. D'autres compilateurs ne le font pas.



-1
votes

Le compilateur n'a vraiment aucun choix dans ce cas. Il ne peut assumer aucun comportement particulier de printf () . En conséquence, il doit supposer que printf () pourrait accrocher à et a tant que une existance elle-même. Par conséquent, un lui-même est en train de vivre dans l'ensemble de la portée où il est défini.


1 commentaires

-1: A est en train de vivre dans l'ensemble de la portée où il est défini . C'est exactement pourquoi il est dans un {...} construct, il n'est donc pas défini à l'extérieur, au moment où le compilateur rencontre B . Donc, le compilateur a un choix.



1
votes

Une grande partie de mon travail consiste à combattre des compilateurs, et je dois dire qu'ils ne font pas toujours ce que nous, les humains, attendent qu'ils font. Même lorsque vous avez programmé le compilateur, vous pouvez toujours être surpris par les résultats, la matrice d'entrée est impossible à prédire 100%.

L'optimisation de la partie du compilateur est très complexe et comme mentionné dans d'autres réponses, ce que vous avez observé pourrait être dû à une réponse volontaire à un cadre, mais cela pourrait simplement être le résultat de l'influence du code environnant, voire même l'absence de cette optimisation dans la logique.

Dans tous les cas, comme le dit Micheal, vous ne devez pas compiler sur le compilateur pour empêcher les débordements de la pile, car vous pourriez simplement pousser le problème à plus tard, lorsque la maintenance de code normale ou un ensemble d'entrées est utilisée, et il est utilisé. s'écrasera beaucoup plus loin dans le pipeline, peut-être entre les mains de l'utilisateur.


0 commentaires