macros #define - Comment imprimer le nom de la macro en c ou c++





langage #ifndef (3)


Étant donné que les macros disparaissent lorsque le préprocesseur fonctionne, ce qui se produit avant l'appel du compilateur, l' APINAME n'existe nulle part dans le code source du compilateur. La seule solution est de trouver une sorte de connexion entre les deux d'une autre manière, par exemple

 struct foo{
    const char *name;
    const char *val;
 } myvar = { "APINAME", APINAME };

Avec une macro, vous pouvez le faire en un seul:

#define APINAME "abc"
#define APINAME_VAR(x, y) struct foo x = { #y, y }

APINAME_VAR(myvar, APINAME)

ou

 cout << "APINAME=" << APINAME << endl

 printf("APINAME=%s\n", APINAME);

Ou, dans le cas de votre macro:

 #define PRINTAPI printf("%s=%s\n", #APINAME, APINAME)

va imprimer APINAME=abc

Comment imprimer le nom de la macro en c ou c ++, par exemple:

#define APINAME abc
#define PRINTAPI(x) printf("x")

Je veux imprimer PRINTAPI (APINAME) et non "abc"




Les macros sont des pré-processeurs et elles seront remplacées par leur instruction associée avant de compiler le code. Donc, vous n'avez aucune chance d'avoir les noms de macros en cours d'exécution. Mais, vous pouvez générer le nom de la chaîne à la compilation:

#define APINAME abc
#define PRINTAPI(x) std::cout << #x << std::endl;

int main()
{
    PRINTAPI(APINAME);
}

Sortie

APINAME

Dans les macros, l'opérateur # rend le paramètre d'entrée à une chaîne littérale (stringify)




Je vais juste donner l'analogie avec laquelle je comprends les modèles de cohérence mémoire (ou modèles de mémoire, pour faire court). Il s'inspire de l'article fondateur de Leslie Lamport intitulé «Time, Clocks, and the Ordering of Events in a Distributed System» . L'analogie est juste et a une signification fondamentale, mais peut être exagérée pour beaucoup de gens. Cependant, j'espère qu'il fournit une image mentale (une représentation picturale) qui facilite le raisonnement sur les modèles de cohérence de la mémoire.

Regardons les historiques de tous les emplacements de mémoire dans un diagramme spatio-temporel dans lequel l'axe horizontal représente l'espace d'adressage (ie, chaque emplacement de mémoire est représenté par un point sur cet axe) et l'axe vertical représente le temps (nous verrons que, en général, il n'y a pas de notion universelle du temps). L'historique des valeurs détenues par chaque emplacement mémoire est donc représenté par une colonne verticale à cette adresse mémoire. Chaque changement de valeur est dû à l'un des threads écrit une nouvelle valeur à cet emplacement. Par une image mémoire , nous entendons l'agrégat / combinaison de valeurs de tous les emplacements de mémoire observables à un moment donné par un thread particulier .

Citant de "Une amorce sur la cohérence de mémoire et la cohérence du cache"

Le modèle de mémoire intuitif (et le plus restrictif) est la cohérence séquentielle (SC) dans laquelle une exécution multithread devrait ressembler à un entrelacement des exécutions séquentielles de chaque thread constitutif, comme si les threads étaient multiplexés dans le temps sur un processeur monocœur.

Cet ordre de mémoire global peut varier d'une exécution du programme à l'autre et peut ne pas être connu à l'avance. La caractéristique de SC est l'ensemble des tranches horizontales dans le diagramme d'adresse-espace-temps représentant des plans de simultanéité (c'est-à-dire des images de mémoire). Sur un plan donné, tous ses événements (ou valeurs de mémoire) sont simultanés. Il y a une notion de temps absolu , dans laquelle tous les sujets s'accordent sur les valeurs de mémoire qui sont simultanées. Dans SC, à chaque instant, il n'y a qu'une seule image mémoire partagée par tous les threads. C'est, à chaque instant de temps, tous les processeurs d'accord sur l'image de la mémoire (c'est-à-dire, le contenu agrégé de la mémoire). Cela implique non seulement que tous les threads affichent la même séquence de valeurs pour tous les emplacements de mémoire, mais également que tous les processeurs observent les mêmes combinaisons de valeurs de toutes les variables. Cela équivaut à dire que toutes les opérations de mémoire (sur tous les emplacements de mémoire) sont observées dans le même ordre total par tous les threads.

Dans les modèles de mémoire détendue, chaque thread découpera l'adresse-espace-temps à sa manière, la seule restriction étant que les tranches de chaque thread ne se croisent pas car tous les threads doivent s'accorder sur l'historique de chaque emplacement mémoire individuel (bien sûr , des tranches de fils différents peuvent, et vont, se croiser). Il n'y a pas de façon universelle de le découper (pas de foliation privilégiée de l'adresse-espace-temps). Les tranches ne doivent pas nécessairement être planes (ou linéaires). Ils peuvent être incurvés et c'est ce qui peut rendre un thread lire des valeurs écrites par un autre thread hors de l'ordre dans lequel ils ont été écrits. Les historiques des différents emplacements mémoire peuvent glisser (ou être étirés) arbitrairement les uns par rapport aux autres . Chaque thread aura un sens différent des événements (ou, de manière équivalente, des valeurs de mémoire) sont simultanés. L'ensemble des événements (ou valeurs de mémoire) qui sont simultanés à un thread ne sont pas simultanés à un autre. Ainsi, dans un modèle de mémoire relaxée, tous les threads observent toujours le même historique (c'est-à-dire, la séquence de valeurs) pour chaque emplacement de mémoire. Mais ils peuvent observer différentes images de mémoire (c'est-à-dire, des combinaisons de valeurs de tous les emplacements de mémoire). Même si deux emplacements de mémoire différents sont écrits par le même thread en séquence, les deux valeurs nouvellement écrites peuvent être observées dans un ordre différent par d'autres threads.

[Image de Wikipedia]

Les lecteurs familiers avec la théorie spéciale de la relativité d'Einstein remarqueront ce à quoi je fais allusion. Traduire les mots de Minkowski dans le domaine des modèles de mémoire: l'espace d'adresse et le temps sont des ombres de l'adresse-espace-temps. Dans ce cas, chaque observateur (c'est-à-dire, thread) projette les ombres des événements (ie, mémoires / charges) sur sa propre ligne du monde (son axe temporel) et son propre plan de simultanéité (son axe adresse-espace) . Les threads dans le modèle de mémoire C ++ 11 correspondent à des observateurs qui se déplacent les uns par rapport aux autres en relativité restreinte. La cohérence séquentielle correspond à l' espace-temps galiléen (c'est -à- dire que tous les observateurs s'accordent sur un ordre absolu d'événements et un sens global de simultanéité).

La ressemblance entre les modèles de mémoire et la relativité restreinte provient du fait que les deux définissent un ensemble d'événements partiellement ordonnés, souvent appelés un ensemble causal. Certains événements (c'est-à-dire les mémoires) peuvent affecter (mais ne pas être affectés par) d'autres événements. Un thread C ++ 11 (ou observateur en physique) n'est rien de plus qu'une chaîne (c'est-à-dire un ensemble totalement ordonné) d'événements (par exemple, des charges de mémoire et stocke des adresses éventuellement différentes).

En relativité, un certain ordre est restauré à l'image apparemment chaotique d'événements partiellement ordonnés, puisque le seul ordre temporel sur lequel tous les observateurs s'accordent est l'ordre parmi les événements "timelike" (c.-à-d. que la vitesse de la lumière dans le vide). Seuls les événements liés au timeline sont ordonnés de manière invariable. Temps en physique, Craig Callender .

Dans le modèle de mémoire C ++ 11, un mécanisme similaire (le modèle de cohérence d'acquisition-libération) est utilisé pour établir ces relations de causalité locales .

Pour fournir une définition de la cohérence de la mémoire et une motivation pour l'abandon de SC, je vais citer "Un Primer sur la cohérence mémoire et la cohérence du cache"

Pour une machine à mémoire partagée, le modèle de cohérence de la mémoire définit le comportement architecturalement visible de son système de mémoire. Le critère d'exactitude pour un comportement de partition de noyau de processeur unique entre " un résultat correct " et " plusieurs alternatives incorrectes ". En effet, l'architecture du processeur exige que l'exécution d'un thread transforme un état d'entrée donné en un état de sortie bien défini, même sur un cœur en panne. Les modèles de cohérence de mémoire partagée, cependant, concernent les charges et les magasins de plusieurs threads et permettent généralement de nombreuses exécutions correctes tout en interdisant plusieurs (plus) incorrectes. La possibilité de plusieurs exécutions correctes est due à l'ISA qui permet à plusieurs threads de s'exécuter simultanément, souvent avec de nombreux entrelacements légaux possibles d'instructions provenant de différents threads.

Les modèles de cohérence de la mémoire détendus ou faibles sont motivés par le fait que la plupart des ordres de mémoire dans les modèles forts sont inutiles. Si un thread met à jour dix éléments de données, puis un indicateur de synchronisation, les programmeurs ne se soucient généralement pas si les éléments de données sont mis à jour les uns par rapport aux autres mais seulement que tous les éléments sont mis à jour. ). Les modèles détendus cherchent à capturer cette flexibilité accrue des commandes et à ne conserver que les commandes " requises " par les programmeurs pour obtenir à la fois des performances et une exactitude accrues de SC. Par exemple, dans certaines architectures, les tampons d'écriture FIFO sont utilisés par chaque noyau pour contenir les résultats des magasins validés (retirés) avant d'écrire les résultats dans les caches. Cette optimisation améliore les performances mais viole SC. Le tampon d'écriture cache la latence de l'entretien d'un magasin manqué. Parce que les magasins sont communs, être capable d'éviter de caler sur la plupart d'entre eux est un avantage important. Pour un processeur monocœur, un tampon d'écriture peut être rendu invisible d'un point de vue architectural en vérifiant qu'une charge vers l'adresse A renvoie la valeur du magasin le plus récent à A même si une ou plusieurs mémoires vers A se trouvent dans le tampon d'écriture. Cela est généralement effectué en passant la valeur du magasin le plus récent à A en charge de A, où «le plus récent» est déterminé par l'ordre du programme, ou en bloquant une charge de A si un magasin vers A se trouve dans le tampon d'écriture . Lorsque plusieurs cœurs sont utilisés, chacun aura son propre tampon d'écriture de contournement. Sans tampons d'écriture, le matériel est SC, mais avec des tampons d'écriture, ce n'est pas le cas, ce qui rend les tampons d'écriture visibles sur le plan architectural dans un processeur multicœur.

Le réapprovisionnement d'un magasin peut se produire si un core dispose d'un tampon d'écriture non-FIFO qui permet aux magasins de partir dans un ordre différent de l'ordre dans lequel ils sont entrés. Cela peut se produire si le premier magasin manque dans le cache pendant que le second frappe ou si le second magasin peut fusionner avec un magasin précédent (c'est-à-dire avant le premier magasin). La réorganisation de charge peut également se produire sur des cœurs planifiés dynamiquement qui exécutent des instructions hors de l'ordre du programme. Cela peut se comporter de la même manière que réorganiser les magasins sur un autre noyau (Pouvez-vous trouver un exemple d'entrelacement entre deux threads?). La réorganisation d'une charge précédente avec un magasin ultérieur (réorganisation du magasin de chargement) peut entraîner de nombreux comportements incorrects, tels que le chargement d'une valeur après avoir libéré le verrou qui la protège (si le magasin est l'opération de déverrouillage). Notez que des réordonnances de charge de stockage peuvent également survenir en raison d'un contournement local dans le tampon d'écriture FIFO couramment implémenté, même avec un noyau qui exécute toutes les instructions dans l'ordre du programme.

Parce que la cohérence du cache et la cohérence de la mémoire sont parfois confondues, il est instructif d'avoir également cette citation:

Contrairement à la cohérence, la cohérence du cache n'est ni visible ni nécessaire pour le logiciel. La cohérence vise à rendre les caches d'un système à mémoire partagée aussi fonctionnellement invisibles que les caches dans un système monocœur. La cohérence correcte garantit qu'un programmeur ne peut pas déterminer si et où un système a des caches en analysant les résultats des charges et des magasins. En effet, une cohérence correcte garantit que les caches n'activent jamais un comportement fonctionnel nouveau ou différent (les programmeurs peuvent toujours être en mesure d'inférer la structure de cache probable en utilisant des informations de synchronisation ). L'objectif principal des protocoles de cohérence de cache est de maintenir l'invariant SWMR (single-writer-multiple-readers) pour chaque emplacement de mémoire. Une distinction importante entre la cohérence et la cohérence est que la cohérence est spécifiée par emplacement de mémoire , alors que la cohérence est spécifiée par rapport à tous les emplacements de mémoire.

En continuant avec notre image mentale, l'invariant SWMR correspond à l'exigence physique qu'il y ait au plus une particule située à n'importe quel endroit, mais il peut y avoir un nombre illimité d'observateurs de n'importe quel endroit.







c++ macros