En pratique, il est difficile (et parfois impossible) de faire fructifier la pile. Comprendre pourquoi nécessite une certaine compréhension de la mémoire virtuelle.
Dans Ye Olde Days d'applications à une seule unité d'exécution et de mémoire contiguë, trois constituaient trois composants d'un espace d'adressage de processus: le code, le segment de mémoire et la pile. La manière dont ces trois éléments étaient disposés dépendait du système d'exploitation, mais le code venait généralement en premier, en commençant par le bas de la mémoire, puis le tas venait grandir, et la pile commençait en haut de la mémoire, puis en bas. Il y avait aussi de la mémoire réservée au système d'exploitation, mais nous pouvons l'ignorer. Les programmes de cette époque présentaient des débordements de pile un peu plus dramatiques: la pile se plantait dans le tas et, en fonction de celui qui était mis à jour en premier, vous utilisiez des données incorrectes ou retourniez d'un sous-programme à une partie arbitraire de la mémoire.
La gestion de la mémoire a quelque peu modifié ce modèle: du point de vue du programme, vous disposiez toujours des trois composants d’une mappe de mémoire de processus, généralement organisés de la même manière, mais chacun des composants était désormais géré en tant que segment indépendant et le MMU signalait la OS si le programme a essayé d'accéder à la mémoire en dehors d'un segment. Une fois que vous avez eu la mémoire virtuelle, il n'était ni nécessaire ni désir de donner à un programme l'accès à tout son espace d'adressage. Donc, les segments ont été assignés des limites fixes.
Alors, pourquoi n'est-il pas souhaitable de donner à un programme l'accès à son espace d'adressage complet? Parce que cette mémoire constitue une "charge engagée" contre le swap; à tout moment, il peut être nécessaire d'écrire toute la mémoire d'un programme pour l'échanger afin de laisser de la place à la mémoire d'un autre programme. Si chaque programme pouvait potentiellement consommer 2 Go d’échange, vous deviez soit fournir suffisamment d’échange pour tous vos programmes, soit courir le risque que deux programmes aient besoin de plus que ce qu’ils pourraient obtenir.
À ce stade, en supposant un espace d'adressage virtuel suffisant, vous pouvez étendre ces segments si nécessaire et le segment de données (segment de mémoire) s'agrandit au fil du temps: vous démarrez avec un petit segment de données et lorsque l'allocateur de mémoire demande plus d'espace lorsque c'est nécessaire. À ce stade, avec une seule pile, il aurait été physiquement possible d'étendre le segment de pile: le système d'exploitation pourrait empêcher la tentative de pousser quelque chose en dehors du segment et ajouter plus de mémoire. Mais ce n'est pas particulièrement souhaitable non plus.
Entrez multi-threading. Dans ce cas, chaque thread a un segment de pile indépendant, toujours de taille fixe. Mais maintenant, les segments sont disposés les uns après les autres dans l'espace d'adressage virtuel. Il est donc impossible de développer un segment sans en déplacer un autre, ce que vous ne pouvez pas faire car le programme comportera potentiellement des pointeurs sur la mémoire. Vous pouvez également laisser un espace entre les segments, mais cet espace serait gaspillé dans presque tous les cas. Une meilleure approche consistait à imposer un fardeau au développeur d’application: si vous aviez vraiment besoin de piles profondes, vous pouvez le spécifier lors de la création du thread.
Aujourd'hui, avec un espace d'adressage virtuel 64 bits, nous pourrions créer efficacement des piles infinies pour un nombre infini de threads. Mais là encore, cela n’est pas particulièrement souhaitable: dans presque tous les cas, un dépassement de pile indique un bogue avec votre code. Vous fournir une pile de 1 Go retarde simplement la découverte de ce bogue.