(fournir ma propre réponse... Je vais attendre quelques jours et s'il n'y a pas de problèmes avec elle -- je la marquerai comme acceptée)
J'ai passé un certain temps à enquêter sur ce sujet et voici ce que j'ai découvert :
- La norme C++ ne précise pas ce qui va se passer dans ce cas.
- Clang et GCC semblent utiliser C++ Itanium ABI
L'ABI d'Itanimum suggère d'utiliser le tas pour les exceptions :
Le stockage est nécessaire pour les exceptions qui sont lancées. Ce stockage doit persister pendant que la pile est déroulée, puisqu'elle sera utilisée par le gestionnaire, et doit être thread-safe. Le stockage des objets d'exception sera donc normalement alloué dans le tas
...
La mémoire sera allouée par le __cxa_allocate_exception
routine de la bibliothèque d'exécution.
Donc, oui... lancer une exception impliquera probablement le verrouillage des mutex et la recherche d'un bloc de mémoire libre :-(
Il mentionne également ceci :
Si __cxa_allocate_exception ne peut pas allouer un objet d'exception sous ces contraintes, elle appelle terminate()
Ouaip... dans GCC et Clang "throw myX() ;" peut tuer votre application et vous ne pouvez rien y faire (peut-être écrire votre propre __cxa_allocate_exception
peut aider -- mais il ne sera certainement pas portable)
C'est encore mieux :
3.4.1 Allocation de l'objet d'exception
La mémoire d'un objet d'exception sera allouée par la fonction __cxa_allocate_exception de la bibliothèque d'exécution, avec les exigences générales décrites à la section 2.4.2. Si l'allocation normale échoue, elle tentera d'allouer l'un des tampons d'urgence, décrits dans la Section 3.3.1, avec les contraintes suivantes :
- La taille de l'objet d'exception, y compris les en-têtes, est inférieure à 1KB.
- Le thread actuel ne détient pas déjà quatre tampons.
- Il y a moins de 16 autres threads détenant des tampons, ou ce fil attendra que l'un des autres libère ses tampons. avant d'en acquérir un.
Oui, votre programme peut tout simplement se bloquer ! Il est vrai que les chances que cela se produise sont faibles -- il faudrait que vous épuisiez la mémoire et que vos threads utilisent les 16 tampons d'urgence et entrent en attente d'un autre thread qui devrait générer une exception. Mais si vous faites des choses avec std::current_exception
(comme enchaîner les exceptions et les passer entre les threads) - ce n'est pas si improbable.
Conclusion :
Il s'agit d'une lacune de la norme C++ : vous ne pouvez pas écrire des programmes fiables à 100 % (qui utilisent des exceptions). L'exemple type est un serveur qui accepte les connexions des clients et exécute les tâches soumises. L'approche évidente pour gérer les problèmes serait de lancer une exception, qui déroulerait tout et fermerait la connexion -- tous les autres clients ne seraient pas affectés et le serveur continuerait à fonctionner (même dans des conditions de mémoire faible). Hélas, un tel serveur est impossible à écrire en C++.
Vous pouvez prétendre que les systèmes modernes (c'est-à-dire Linux) tueront un tel serveur avant que nous n'atteignions cette situation de toute façon. Mais (1) ce n'est pas un argument ; (2) le gestionnaire de mémoire peut être configuré pour surcommettre ; (3) le tueur OOM ne sera pas déclenché pour une application 32 bits fonctionnant sur un matériel 64 bits avec suffisamment de mémoire (ou si l'application limite artificiellement l'allocation de mémoire).
Personnellement, je suis assez énervé par cette découverte. Pendant de nombreuses années, j'ai prétendu que mon code gérait gracieusement le hors mémoire. Il s'avère que j'ai menti à mes clients :-( Autant commencer à intercepter l'allocation de mémoire, appeler std::terminate
et traiter toutes les fonctions connexes comme noexcept
-- Cela me facilitera certainement la vie (en matière de codage). Pas étonnant qu'ils utilisent encore Ada pour programmer les fusées.