Ce n'est pas vraiment une réintégration ; vous n'exécutez pas une fonction deux fois dans le même thread (ou dans des threads différents). Vous pouvez l'obtenir via la récursivité ou en passant l'adresse de la fonction actuelle comme argument de pointeur de fonction de rappel à une autre fonction. (Et ce ne serait pas dangereux car ce serait synchrone).
Ceci est tout simplement de la course aux données vanille UB (Undefined Behavior) entre un gestionnaire de signal et le thread principal: seul sig_atomic_t
est garanti sans danger pour cela . D'autres peuvent arriver à fonctionner, comme dans votre cas où un objet de 8 octets peut être chargé ou stocké avec une instruction sur x86-64, et le compilateur arrive à choisir cet asm. (Comme le montre la réponse de @ icarus).
Voir Programmation MCU - L'optimisation C ++ O2 se casse en boucle - un gestionnaire d'interruption sur un microcontrôleur monocœur est fondamentalement la même chose qu'un gestionnaire de signal dans un programme à thread unique. Dans ce cas, le résultat de l'UB est qu'une charge s'est hissée hors d'une boucle.
Votre cas de test de déchirure se produisant réellement en raison de la course aux données UB a probablement été développé / testé en mode 32 bits, ou avec un compilateur plus ancien qui chargeait les membres de la structure séparément.
Dans votre cas, le compilateur peut optimiser les magasins hors de la boucle infinie car aucun programme sans UB n'a pu les observer. data
n'est pas _Atomic
ouvolatile
, et il n'y a pas d'autres effets secondaires dans la boucle. Il n'y a donc aucun moyen pour un lecteur de se synchroniser avec cet écrivain. Cela se produit en fait si vous compilez avec l'optimisation activée ( Godbolt montre une boucle vide en bas de main). J'ai également changé la structure en deux long long
, et gcc utilise un seul movdqa
magasin de 16 octets avant la boucle. (Ce n'est pas garanti atomique, mais c'est en pratique sur presque tous les processeurs, en supposant qu'il est aligné, ou sur Intel ne franchit tout simplement pas une limite de ligne de cache. Pourquoi l'affectation d'entiers sur une variable naturellement alignée atomique sur x86? )
Donc, la compilation avec l'optimisation activée casserait également votre test et vous montrerait la même valeur à chaque fois. C n'est pas un langage d'assemblage portable.
volatile struct two_int
forcerait également le compilateur à ne pas les optimiser, mais ne le forcerait pas à charger / stocker la structure entière atomiquement. (Cela ne l' empêcherait pas non plus .) Notez que cela volatile
n'évite pas l' UB de course aux données, mais dans la pratique, cela suffit pour la communication entre les threads et comment les gens ont construit des atomes atomiques roulés à la main (avec asm en ligne) avant C11 / C ++ 11, pour les architectures CPU normales. Ils cache cohérent donc volatile
est en pratique essentiellement similaire à _Atomic
avecmemory_order_relaxed
pour pur-charge et pure magasin, si elle est utilisée pour les types assez étroit que le compilateur utilisera une instruction unique afin de ne pas déchirer. Et bien sûrvolatile
n'a aucune garantie de la norme ISO C par rapport à l'écriture de code qui se compile dans le même asm using _Atomic
et mo_relaxed.
Si vous aviez une fonction qui fonctionnait global_var++;
sur un int
ou long long
que vous exécutez à partir de main et de manière asynchrone à partir d'un gestionnaire de signaux, ce serait un moyen d'utiliser la rentrée pour créer une UB de course de données.
Selon la façon dont il a été compilé (vers une destination mémoire inc ou add, ou pour séparer load / inc / store), il serait atomique ou non par rapport aux gestionnaires de signaux dans le même thread. Voir Est-ce que num ++ peut être atomique pour 'int num'? pour en savoir plus sur l'atomicité sur x86 et en C ++. (C11 stdatomic.h
et l' _Atomic
attribut fournissent des fonctionnalités équivalentes au std::atomic<T>
modèle de C ++ 11 )
Une interruption ou une autre exception ne peut pas se produire au milieu d'une instruction, donc un ajout de destination de mémoire est atomique wrt. le contexte bascule sur un processeur monocœur. Seul un écrivain DMA (cohérent en cache) peut "monter" un incrément à partir d'un add [mem], 1
sans lock
préfixe sur un processeur monocœur. Il n'y a pas d'autres cœurs sur lesquels un autre thread pourrait s'exécuter.
C'est donc similaire au cas des signaux: un gestionnaire de signaux s'exécute au lieu de l'exécution normale du thread qui gère le signal, il ne peut donc pas être géré au milieu d'une instruction.