Lorsque vous allouez de la mémoire sur le tas, la seule limite est libre RAM (ou mémoire virtuelle). Cela fait Go de mémoire.
Alors pourquoi la taille de la pile est-elle si limitée (environ 1 Mo)? Quelle raison technique vous empêche de créer de très gros objets sur la pile?
Update : Mon intention n'est peut-être pas claire, je ne souhaite pas allouer d'énormes objets sur la pile et je n'ai pas besoin d'une pile plus grande. Cette question n'est que pure curiosité.
Mon intuition est la suivante. La pile n'est pas aussi facile à gérer que le tas. La pile doit être stockée dans des emplacements de mémoire continus. Cela signifie que vous ne pouvez pas allouer la pile de manière aléatoire en fonction des besoins, mais vous devez au moins réserver des adresses virtuelles à cette fin. Plus la taille de l'espace d'adressage virtuel réservé est grande, moins vous pourrez créer de threads.
Par exemple, une application 32 bits dispose généralement d'un espace d'adressage virtuel de 2 Go. Cela signifie que si la taille de la pile est de 2 Mo (par défaut dans pthreads), vous pouvez créer un maximum de 1024 threads. Cela peut être petit pour des applications telles que les serveurs Web. Augmenter la taille de la pile à 100 Mo (par exemple, vous réservez 100 Mo, mais ne lui allouez pas nécessairement 100 Mo immédiatement) limiterait le nombre de threads à environ 20, ce qui peut être limité même pour les applications à interface graphique simple.
Une question intéressante est: pourquoi avons-nous toujours cette limite sur les plates-formes 64 bits? Je ne connais pas la réponse, mais je suppose que les utilisateurs sont déjà habitués à certaines "pratiques optimales de pile": veillez à allouer des objets énormes sur le tas et, si nécessaire, augmentez manuellement la taille de la pile. Par conséquent, personne n'a jugé utile d'ajouter un "énorme" support de pile sur les plates-formes 64 bits.
Un aspect que personne n'a encore mentionné:
Une taille de pile limitée est un mécanisme de détection d'erreur et de confinement.
Généralement, le travail principal de la pile en C et C++ consiste à suivre la pile d'appels et les variables locales. Si la pile dépasse les limites, il s'agit presque toujours d'une erreur de conception et/ou du comportement de l'application .
Si la pile était autorisée à grossir de manière arbitraire, ces erreurs (telles que la récursion infinie) seraient détectées très tard, uniquement une fois que les ressources du système d'exploitation ont été épuisées. Ceci est empêché en fixant une limite arbitraire à la taille de la pile. La taille réelle n’est pas très importante, à part le fait qu’elle est suffisamment petite pour empêcher la dégradation du système.
C'est juste une taille par défaut. Si vous avez besoin de plus, vous pouvez en avoir plus - le plus souvent en demandant à l'éditeur de liens d'allouer de l'espace supplémentaire à la pile.
L'inconvénient d'avoir de grandes piles est que si vous créez plusieurs threads, ils auront besoin d'une pile chacun. Si toutes les piles allouent plusieurs Mo, mais ne l'utilisent pas, l'espace sera perdu.
Vous devez trouver le bon équilibre pour votre programme.
Certaines personnes, comme @BJovke, pensent que la mémoire virtuelle est essentiellement libre. Il est vrai qu'il n'est pas nécessaire de disposer d'une mémoire physique sauvegardant toute la mémoire virtuelle. Vous devez au moins pouvoir donner des adresses à la mémoire virtuelle.
Cependant, sur un PC 32 bits typique, la taille de la mémoire virtuelle est identique à celle de la mémoire physique, car nous ne disposons que de 32 bits pour toute adresse, virtuelle ou non.
Comme tous les threads d'un processus partagent le même espace d'adressage, ils doivent le diviser entre eux. Et une fois que le système d'exploitation a pris sa part, il ne reste "que" 2 ou 3 Go pour une application. Et cette taille est la limite pour les deux le physique et la mémoire virtuelle, car il n’ya tout simplement plus d’adresses.
D'une part, la pile est continue, donc si vous allouez 12 Mo, vous devez supprimer 12 Mo si vous voulez aller en-dessous de ce que vous avez créé. De plus, déplacer des objets devient beaucoup plus difficile. Voici un exemple concret qui peut rendre les choses plus faciles à comprendre:
Supposons que vous empilez des boîtes autour d'une pièce. Ce qui est plus facile à gérer:
Ces deux exemples sont des généralisations grossières et certains points sont manifestement faux dans l'analogie, mais ils sont suffisamment proches pour que cela puisse vous aider à voir les avantages dans les deux cas.
Beaucoup des choses pour lesquelles vous pensez avoir besoin d'un gros stack peuvent être faites d'une autre manière.
Les "algorithmes" de Sedgewick ont quelques bons exemples de "suppression" de la récursivité d'algorithmes récursifs tels que QuickSort, en remplaçant la récursion par l'itération. En réalité, l'algorithme est toujours récursif et il y a toujours une pile, mais vous allouez la pile de tri sur le tas plutôt que d'utiliser la pile d'exécution.
(Je préfère la deuxième édition, avec des algorithmes donnés en Pascal. On peut s'en servir pour huit dollars.)
Une autre façon de voir les choses est que si vous pensez avoir besoin d'un gros stack, votre code est inefficace. Il y a une meilleure façon d'utiliser moins de pile.
Pensez à la pile dans l’ordre de presque proche. Les registres sont proches du processeur (rapide), la pile est un peu plus loin (mais reste relativement proche) et le tas est éloigné (accès lent).
La pile réside dans le tas de parcours, mais comme elle est utilisée de manière continue, elle ne quitte probablement pas le ou les caches de la CPU, ce qui la rend plus rapide que l'accès moyen au tas .. dimensionné; pour le garder en cache autant que possible. L'allocation de gros objets de pile (éventuellement en redimensionnant automatiquement lorsque vous rencontrez des débordements) va à l'encontre de ce principe.
C'est donc un bon paradigme pour la performance, pas seulement un vestige du passé.