Maison > Article > développement back-end > Portabilité et développement multiplateforme du C++ (article long)
Aperçu
Aujourd'hui, nous parlerons de la portabilité du C++. Si vous utilisez habituellement le C++ pour le développement et que vous n'êtes pas très clair sur les problèmes de portabilité du C++, alors je vous suggère de jeter un œil à cette série. Même si vous n’avez actuellement pas besoin de développement multiplateforme, il est toujours utile de connaître la portabilité.
Le sujet de la portabilité C++ est vaste, couvrant de nombreux aspects tels que les compilateurs, les systèmes d'exploitation, les systèmes matériels, etc. Chaque aspect a beaucoup de contenu. Compte tenu de mes capacités et de mon énergie limitées, je ne peux présenter que les problèmes les plus courants dans chaque aspect, à titre de référence.
Je le présenterai plus tard à partir du compilateur, de la syntaxe C++, du système d'exploitation, des bibliothèques tierces, des outils auxiliaires, du processus de développement, etc.
Compilateur
Lors du processus de développement multiplateforme, de nombreux problèmes sont liés au compilateur. Parlons donc d’abord des problèmes liés au compilateur.
Sélection du compilateur
Tout d'abord, GCC est une priorité à prendre en charge, car GCC est disponible sur presque toutes les plateformes de systèmes d'exploitation. Cela devient essentiellement un compilateur universel. Si votre code peut être compilé et transmis par GCC sur la plateforme A, puis compilé avec une version similaire de GCC sur la plateforme B, il n'y aura généralement pas vraiment de problème. Par conséquent, GCC doit absolument envisager de le soutenir.
Deuxièmement, réfléchissez à la prise en charge du compilateur local. Le compilateur dit local est le compilateur produit par le fabricant du système d'exploitation. Par exemple : le compilateur natif pour Windows est Visual C++. Le compilateur local relatif à Solaris est le CC de SUN. Si vous êtes plus sensible aux performances ou souhaitez utiliser les fonctionnalités avancées de certains compilateurs locaux, vous devrez peut-être envisager de prendre en charge les compilateurs locaux ainsi que GCC.
Avertissement de compilation
Le compilateur est l'ami du programmeur. De nombreux problèmes potentiels (y compris la portabilité) peuvent être découverts et recevoir des avertissements par le compilateur si vous prêtez attention à ces messages d'avertissement, vous pouvez en réduire beaucoup. . Par conséquent, je recommande fortement :
1. Augmentez le niveau d'avertissement du compilateur
2. N'ignorez pas facilement les informations d'avertissement du compilateur.
Compilateur croisé
Voir la définition du compilateur croisé dans "Wikipédia". En termes simples, cela signifie compiler un programme binaire sur la plateforme A et l'exécuter sur la plateforme B. Supposons que l'application que vous souhaitez développer fonctionne sur Solaris, mais que vous ne disposez pas d'une machine SPARC capable d'exécuter Solaris. À l'heure actuelle, un compilateur croisé peut s'avérer utile. Dans des circonstances normales, GCC est utilisé pour créer un compilateur croisé. En raison de contraintes d'espace, nous n'en discuterons pas en profondeur ici. Les étudiants intéressés peuvent se référer à « ici ».
Gestion des exceptions
En raison de l'espace limité du post précédent "Grammaire", je n'ai pas eu le temps de parler des exceptions. Je vais maintenant retirer les parties liées aux exceptions et en parler séparément.
Soyez prudent lorsque new ne parvient pas à allouer de la mémoire
Le code généré par les premiers compilateurs à l'ancienne renverra un pointeur nul si new échoue. Le Borland C++ 3.1 que j'utilisais à l'époque semble être comme ça. Ce type de compilateur devrait être rare maintenant. Si le compilateur que vous utilisez actuellement se comporte toujours ainsi, vous avez des problèmes. Vous pouvez envisager de surcharger l'opérateur new pour lever une exception bad_alloc afin de faciliter la gestion des exceptions.
Un compilateur légèrement plus récent ne renverra pas simplement un pointeur nul. Lorsque l'opérateur new constate que la mémoire est hors de contrôle, selon la norme (voir la section 18.4.2 de la norme C++03), il doit appeler la fonction new_handler (le prototype est typedef void (*new_handler)();). La norme recommande que la fonction new_handler fasse les trois choses suivantes :
1. Essayez d'obtenir plus de mémoire
2. Lancez une exception bad_alloc
3. processus.
Puisque la fonction new_handler peut être réinitialisée (en appelant set_new_handler), elle peut avoir les comportements ci-dessus.
En résumé, si new ne parvient pas à allouer de la mémoire, il existe trois possibilités :
1. Renvoie un pointeur nul
2. Lance une exception
3.
Si vous souhaitez que votre code soit plus portable, vous devez prendre en compte les trois situations.
Utilisez les spécifications d'exception avec prudence
À mon avis, les spécifications d'exception ne sont pas une bonne chose. Si vous ne me croyez pas, vous pouvez lire l'article 75 des "Normes de codage C++ - 101 règles, directives et meilleures pratiques". . (Les inconvénients spécifiques seront discutés plus tard dans un article séparé sur les exceptions C++ et la gestion des erreurs.) Plus près de chez nous, selon la norme (voir le chapitre 18.6.2 de la norme 03), si une exception levée par une fonction n'est pas incluse dans la spécification d'exception de la fonction, alors unexceptiond() doit être appelé. Mais tout le code généré par le compilateur n’adhère pas aux normes (comme certaines versions des compilateurs VC). Si le compilateur que vous devez prendre en charge se comporte de manière incohérente avec les spécifications d'exception, envisagez de supprimer la déclaration de spécification d'exception.
Ne lancez pas d'exceptions entre les modules
Le module mentionné ici fait référence à la bibliothèque dynamique. Si votre programme contient plusieurs bibliothèques dynamiques, ne lancez pas d'exceptions en dehors des fonctions exportées du module. Après tout, il n’existe pas encore de norme ABI pour C++ (on estime qu’il n’y en aura peut-être pas à l’avenir), et lancer des exceptions entre modules entraînera de nombreux comportements imprévisibles.
N’utilisez pas la gestion structurée des exceptions (SEH)
Si vous n’avez jamais entendu parler de SEH, faites comme si je ne l’avais pas mentionné et sautez ce paragraphe. Si vous avez l'habitude d'utiliser SEH auparavant, vous devez changer cette habitude avant d'envisager d'écrire du code multiplateforme. Le code contenant SEH ne peut être compilé que sur la plateforme Windows et ne sera certainement pas multiplateforme.
À propos de catch(…)
Il va de soi que l'instruction catch(…) ne peut capturer que les types d'exceptions C++ et est impuissante face aux exceptions non C++ telles que les violations d'accès et les erreurs de division par zéro. Mais dans certains cas (comme certains compilateurs VC), tels que les violations d'accès et les erreurs de division par zéro, peuvent également être détectées par catch(...). Par conséquent, si vous souhaitez que votre code soit portable, vous ne pouvez pas vous fier au comportement catch(...) ci-dessus dans la logique de votre programme.
Lié au système matériel
Le sujet dont nous avons parlé cette fois est principalement lié au système matériel. Par exemple, si votre programme doit prendre en charge différents types de processeurs (x86, SPARC, PowerPC) ou le même type de processeur avec différentes longueurs de mots (comme x86 et x86-64), vous devez alors vous soucier du système matériel. .
Taille des types de base
La taille (nombre d'octets occupés) des types de base en C++ changera à mesure que la longueur du mot CPU change. Par conséquent, si vous souhaitez exprimer le nombre d'octets occupés par un int, n'écrivez pas directement "4" (d'ailleurs, écrire directement "4" viole également le tabou du nombre magique, voir ici pour plus de détails), mais devez écrire " sizeof(int)"; à l'inverse, si vous souhaitez définir un entier signé dont la taille doit être de 4 octets, n'utilisez pas int directement, mais utilisez un type de longueur fixe pré-typedef (comme int32_t de la bibliothèque boost, ACE bibliothèque ACE_INT32, etc.).
J'ai presque oublié que la taille du pointeur présente également les problèmes ci-dessus, alors soyez prudent.
Ordre des octets
Si vous n'avez pas entendu parler de « l'ordre des octets », veuillez lire « Wikipédia ». Pour utiliser une analogie populaire, s'il y a un entier de 4 octets 0x01020304 sur une machine big-endian, il deviendra 0x04030201 lorsqu'il sera transmis à une machine small-endian via le réseau ou un fichier, on dit qu'il existe également un support ; machine endian. (Mais je ne suis pas entré en contact avec elle), l'entier ci-dessus deviendra 0x02010403.
Si l'application que vous écrivez implique une communication réseau, n'oubliez pas de la traduire entre le code hôte et le code réseau ; si elle implique le transfert de fichiers binaires entre machines, n'oubliez pas d'effectuer des conversions similaires ;
Alignement de la mémoire
Si vous ne savez pas ce qu'est « l'alignement de la mémoire », veuillez lire « Wikipédia ». Pour faire simple, pour des raisons de performances de traitement du processeur, les données de la structure ne sont pas proches les unes des autres, mais doivent être séparées par un certain espace. Dans ce cas, l'adresse de chaque donnée de la structure est exactement un multiple entier d'une certaine longueur de mot.
Étant donné que les détails de l'alignement de la mémoire ne sont pas définis dans la norme C++, votre code ne peut pas s'appuyer sur les détails de l'alignement. Partout où la taille d’une structure est calculée, sizeof() est écrit honnêtement.
Certains compilateurs prennent en charge l'instruction de prétraitement #pragma pack (qui peut être utilisée pour modifier la longueur du mot d'alignement). Cependant, cette syntaxe n'est pas prise en charge par tous les compilateurs, utilisez-la donc avec prudence.
Opération de décalage
Pour l'opération de décalage à droite d'entiers signés, certains systèmes utilisent le décalage arithmétique à droite par défaut (le bit de signe le plus élevé reste inchangé) et un décalage logique à droite par défaut (le bit de signe le plus élevé est rempli par 0) . Par conséquent, n’effectuez pas de décalage vers la droite sur des entiers signés. À propos, même s'il n'y a pas de problèmes de portabilité, essayez d'utiliser le moins possible les opérateurs de décalage dans votre code. Ceux qui essaient d'utiliser les opérations de décalage pour améliorer les performances devraient y prêter plus d'attention. Non seulement cela est peu lisible, mais c'est également ingrat. Tant que le compilateur n'est pas trop retardé mentalement, il gérera automatiquement cette optimisation pour vous, sans que le programmeur n'ait à s'en soucier.
Système d'exploitation
Le post précédent mentionnait des sujets liés au "système matériel". Aujourd'hui, parlons des sujets liés au système d'exploitation. Il y a beaucoup de questions triviales liées au système d'exploitation dans le développement multiplateforme C++, donc aujourd'hui je serai verbeux pendant longtemps, pardonnez-moi :-)
Afin de ne pas tourner autour du pot, ci-dessous, Linux et. divers Unix sont collectivement appelés systèmes Posix.
Système de fichiers (FileSystem, ci-après dénommé FS)
La plupart des novices qui viennent de commencer le développement multiplateforme rencontreront des problèmes liés à FS. Parlons donc d’abord de FS. Pour résumer, les différences FS faciles à rencontrer au cours du développement incluent principalement les suivantes : différences dans les séparateurs de répertoires ; différences dans la sensibilité à la casse ; différences dans les caractères interdits dans les chemins ;
Afin de faire face aux différences ci-dessus, vous devez prêter attention aux points suivants :
1. La dénomination des fichiers et des répertoires doit être standardisée
Lorsque vous nommez des fichiers et des répertoires, essayez d'utiliser uniquement des lettres et des chiffres. Ne placez pas deux fichiers portant des noms similaires (seule la casse est différente dans les noms, comme foo.cpp et Foo.cpp) dans le même répertoire. N'utilisez pas certains mots réservés au système d'exploitation (tels que aux, con, nul, prn) comme noms de fichiers ou de répertoires.
Pour ajouter, la dénomination qui vient d'être mentionnée inclut les fichiers de code source, les fichiers binaires et autres fichiers créés lors de l'exécution.
2. Les instructions #include doivent être standardisées
Lorsque vous écrivez des instructions #include, veillez à utiliser des barres obliques "/" (plus courantes) au lieu des barres obliques inverses "" (disponibles uniquement sous Windows). Les noms de fichiers et de répertoires dans l'instruction #include doivent être exactement dans la même casse que les noms réels.
3. Si le code implique des opérations FS, essayez d'utiliser des bibliothèques prêtes à l'emploi
Il existe déjà de nombreuses bibliothèques tierces matures pour FS (telles que boost::filesystem). Si votre code implique des opérations FS (telles que la traversée de répertoires), essayez d'utiliser ces bibliothèques tierces, ce qui peut vous faire économiser beaucoup de travail.
★Carriage Return CR/Line Feed LF pour les fichiers texte
Ce problème ennuyeux est causé par la gestion incohérente du retour chariot/saut de ligne par plusieurs systèmes d'exploitation bien connus. La situation actuelle est la suivante : Windows utilise à la fois CR et LF ; Linux et la plupart des Unix utilisent LF ; la série Mac d'Apple utilise CR.
Pour la gestion du code source, heureusement, de nombreux logiciels de gestion de versions (comme CVS, SVN) géreront intelligemment ce problème, permettant de récupérer le code source local depuis la base de code pour l'adapter au format local.
Si votre programme doit traiter des fichiers texte au moment de l'exécution, veuillez faire attention à la différence entre l'ouverture en mode texte et l'ouverture en mode binaire. Envisagez également une gestion appropriée s’il s’agit de transférer des fichiers texte entre différents systèmes.
★Chemin de recherche de fichiers (incluant la recherche de fichiers exécutables et de bibliothèques dynamiques)
Sous Windows, si vous souhaitez exécuter un fichier ou charger une bibliothèque dynamique, vous effectuerez généralement une recherche dans le répertoire courant ce n'est pas toujours le cas ; dans les systèmes Posix. Donc si votre application implique le démarrage de processus ou le chargement de bibliothèques dynamiques, faites attention à cette différence.
★Variables d'environnement
Concernant le problème du chemin de recherche mentionné ci-dessus, certains étudiants souhaitent introduire le chemin actuel en modifiant PATH et LD_LIBRARY_PATH. Si vous utilisez cette méthode, il est recommandé de modifier uniquement les variables d'environnement au niveau du processus et de ne pas modifier les variables d'environnement au niveau du système (les modifications au niveau du système peuvent affecter d'autres logiciels sur la même machine, provoquant des effets secondaires).
★Bibliothèque dynamique
Si votre application utilise une bibliothèque dynamique, il est fortement recommandé que la bibliothèque dynamique exporte les fonctions standard de style C (essayez de ne pas exporter de classes). Si vous chargez une bibliothèque dynamique dans un système Posix, n'oubliez pas d'utiliser l'indicateur RTLD_GLOBAL avec prudence. Cet indicateur activera la table de symboles globale, ce qui peut provoquer des conflits de noms de symboles entre plusieurs bibliothèques dynamiques (une fois que cela se produit, d'incroyables erreurs d'exécution se produiront, ce qui est extrêmement difficile à déboguer).
★Service/Guardian Process
Si vous n'êtes pas clair sur les concepts de services et de processus de garde, veuillez lire Wikipédia (ici et ici). Pour faciliter la description, ils sont collectivement appelés services ci-dessous.
La plupart des modules développés en C++ étant des modules de fond, des problèmes de service sont souvent rencontrés. L'écriture de services nécessite l'appel de plusieurs API liées au système, ce qui entraîne un couplage étroit avec le système d'exploitation, ce qui rend difficile l'utilisation d'un seul ensemble de code. Par conséquent, une meilleure méthode consiste à extraire un shell de service général, puis à monter le code de logique métier en tant que bibliothèque dynamique sous celui-ci. Dans ce cas, au moins un ensemble de codes de logique métier est garanti ; bien que deux ensembles de codes shell de service soient requis (un pour Windows et un pour Posix), ils sont indépendants de l'entreprise et peuvent être facilement réutilisés.
★Taille de pile par défaut
Différents systèmes d'exploitation, la taille de pile par défaut varie considérablement, allant de plusieurs dizaines de Ko (on dit que Symbian n'a que 12 Ko, ce qui est vraiment avare) à plusieurs Mo. Par conséquent, vous devez vous renseigner à l'avance sur la taille de pile par défaut du système cible. Si vous rencontrez un système avare comme Symbian, vous pouvez envisager d'utiliser les options du compilateur pour l'augmenter. Bien sûr, il est également important de développer la bonne habitude de "ne pas définir de grands tableaux/gros objets sur la pile", sinon la pile éclatera, quelle que soit sa taille.
Multi-threading
Les articles que j'ai écrits au cours du dernier mois ont été assez compliqués, ce qui a fait que cette série n'a pas été mise à jour depuis longtemps. En conséquence, un autre internaute m'a exhorté dans les commentaires, ce qui m'a un peu gêné. Dépêchez-vous et terminez l'article multithread aujourd'hui. La dernière fois que j'ai parlé des systèmes d'exploitation, j'ai abordé beaucoup de sujets divers car les sujets liés aux systèmes d'exploitation étaient relativement triviaux. A ce moment-là, j'ai vu que l'article était un peu long, j'ai donc laissé les parties multi-processus et multi-thread à plus tard.
★Compilateur
◇À propos des options de la bibliothèque d'exécution C
Parlons d'abord d'une question très basique : à propos des paramètres de la bibliothèque d'exécution C (ci-après dénommée CRT : C Run-Time). Je ne voulais pas parler d'un problème aussi mineur, mais il y a plusieurs personnes autour de moi qui ont subi des pertes dans cet endroit, alors autant en parler.
La plupart des compilateurs C++ sont livrés avec CRT (peut-être plusieurs). Le CRT fourni avec certains compilateurs peut être divisé en CRT monothread et CRT multithread en fonction de la prise en charge des threads. Lorsque vous souhaitez réaliser un développement multi-thread, n'oubliez pas de vous assurer que le projet C++ concerné utilise un CRT multi-thread. Sinon, ce sera une vilaine mort.
Surtout lorsque vous utilisez Visual C++ pour créer des projets d'ingénierie, vous devez être plus prudent. Si le projet nouvellement créé ne contient pas de MFC (y compris les projets Console et les projets Win32), le paramètre par défaut du projet sera d'utiliser "CRT monothread", comme indiqué dans la figure ci-dessous :
◇À propos des options d'optimisation
« Options d'optimisation » est un autre sujet critique lié au compilateur. Certains compilateurs proposent des options d'optimisation considérées comme géniales, mais certaines options d'optimisation peuvent présenter des risques potentiels. Le compilateur peut perturber l'ordre d'exécution des instructions de sa propre initiative, ce qui entraîne des conditions de course aux threads inattendues (Race Condition, voir « ici » pour une explication détaillée). Liu Weipeng a donné plusieurs exemples typiques dans "Modèle de mémoire multithread C++".
Il est recommandé d'utiliser uniquement les options d'optimisation de vitesse habituelles du compilateur. Les effets supplémentaires d’autres options d’optimisation sophistiquées ne sont peut-être pas évidents, mais les risques potentiels ne sont pas minimes. Cela n'en vaut vraiment pas la peine.
Prenons GCC comme exemple : il est recommandé d'utiliser l'option -O2 (en fait, -O2 est un ensemble d'options. Il n'est pas nécessaire de prendre le risque d'utiliser -O3 (sauf si vous avez un très bon). raison). En plus de -O2 et -O3, GCC propose un grand nombre (estimé à des centaines) d'autres options d'optimisation. Si vous envisagez d'utiliser l'une des options, vous devez d'abord comprendre ses caractéristiques et ses effets secondaires possibles, sinon vous ne saurez pas comment mourir à l'avenir.
★Sélection de la bibliothèque de threads
Étant donné que la norme C++03 actuelle n'implique pratiquement pas de contenu lié aux threads (même si C++0x inclut une bibliothèque standard pour les threads à l'avenir, la prise en charge des fabricants de compilateurs pourrait ne pas être complet à court terme), donc pendant longtemps encore, le support multi-thread multiplateforme dépendra encore de bibliothèques tierces. Le choix de la bibliothèque de threads est donc très important. Voici une brève introduction à plusieurs bibliothèques de threads multiplateformes bien connues.
◇ACE
Parlons d’abord d’ACE, une bibliothèque avec une longue histoire. Si vous ne l'avez jamais rencontré auparavant, regardez d'abord l'alphabétisation « ici ». À en juger par le nom complet d'ACE (Adaptive Communication Environment), il devrait être basé sur la « communication ». Cependant, la prise en charge par ACE des activités annexes du « multi-threading » est toujours très complète, comme les verrous mutex (ACE_Mutex), les variables de condition (ACE_Condition), les sémaphores (ACE_Semaphore), les barrières (ACE_Barrier), les opérations atomiques (ACE_Atomic_Op), etc. . Certains types tels que ACE_Mutex sont également subdivisés en verrous de lecture-écriture de thread (ACE_RW_Thread_Mutex), verrous récursifs de thread (ACE_Recursive_Thread_Mutex), etc.
En plus d'une prise en charge complète, ACE présente un autre avantage évident, à savoir qu'il prend en charge diverses plates-formes de systèmes d'exploitation et leurs propres compilateurs. Y compris certains compilateurs anciens (tels que VC6), il peut également le prendre en charge (le support mentionné ici signifie non seulement qu'il peut être compilé, mais qu'il peut également fonctionner de manière stable). Cet avantage est tout à fait évident pour le développement multiplateforme.
Quelles sont les lacunes ? Depuis qu'ACE a été lancé très tôt (probablement au milieu des années 1990), de nombreuses anciennes fonctionnalités de C++ n'avaient pas encore été publiées (sans parler des nouvelles fonctionnalités), de sorte que le style général d'ACE semblait démodé et bien inférieur à Boost. Tellement à la mode et avant-gardiste.
◇boost::thread
boost::thread contraste fortement avec ACE. Cette chose semble avoir été introduite depuis la version boost 1.32 et est plus jeune qu'ACE. Cependant, grâce au soutien d’un groupe d’experts en boost, le développement est assez rapide. Depuis la version 1.38 actuelle de Boost, il peut également prendre en charge de nombreuses fonctionnalités (mais cela ne semble pas autant qu'ACE). Compte tenu du fait que de nombreux membres du comité de standard C++ se rassemblent au sein de la communauté boost, au fil du temps, boost::thread finira par devenir l'étoile montante des threads C++, avec un bel avenir !
L'inconvénient de boost::thread est qu'il ne prend pas en charge de nombreux compilateurs, en particulier certains compilateurs à l'ancienne (de nombreuses sous-bibliothèques boost ont ce problème, principalement parce qu'elles utilisent une syntaxe de modèle avancée). C'est un problème évident pour le multiplateforme.
◇wxWidgets et QT
wxWidgets et QT sont tous deux des bibliothèques d'interface graphique, mais ils ont également un support intégré pour les threads. Pour une introduction aux threads wxWidgets, vous pouvez voir « ici », et pour une introduction aux threads QT, vous pouvez voir « ici ». Ces deux bibliothèques prennent en charge de manière similaire les threads et fournissent toutes deux des mécanismes couramment utilisés tels que le mutex, la condition et le sémaphore. Cependant, les fonctionnalités ne sont pas aussi riches que celles d’ACE.
◇Comment peser
Pour développer un logiciel GUI et utiliser déjà wxWidgets ou QT, vous pouvez directement utiliser leurs bibliothèques de threads intégrées (à condition d'utiliser uniquement les fonctions de base des threads). En raison de leur bibliothèque de threads intégrée, les fonctionnalités sont un peu minces. Si vous avez besoin d'une fonctionnalité de threading avancée, envisagez de la remplacer par boost::thread ou ACE.
Quant au choix entre boost::thread et ACE, il dépend principalement des besoins du logiciel. Si vous souhaitez prendre en charge des plates-formes nombreuses et complexes, il est recommandé d'utiliser ACE pour éviter les problèmes qui ne sont pas pris en charge par le compilateur. Si vous n'avez besoin de prendre en charge que quelques plates-formes grand public (telles que Windows, Linux, Mac), il est recommandé d'utiliser boost::thread. Après tout, les compilateurs sur les systèmes d’exploitation traditionnels disposent toujours d’un bon support pour Boost.
★Précautions de programmation
En fait, il y a beaucoup de choses auxquelles il faut prêter attention dans le développement multithread. Je ne peux que brièvement énumérer quelques choses qui sont plus impressionnantes.
◇À propos de volatile
En parlant des pièges que l'on peut rencontrer dans la programmation multi-thread, il faut mentionner le mot-clé volatile. Si vous n'y connaissez pas grand-chose, lisez d'abord "ici" pour en savoir plus. Étant donné que les normes C++ 98 et C++ 03 ne définissent pas de modèle de mémoire multithread, les normes n'ont que quelque chose à voir avec la volatilité et les threads. En conséquence, une grande partie de la salive de la communauté C++ se concentre sur le volatile (y compris la salive de nombreux experts C++). Compte tenu de cela, je n’entrerai pas ici dans les détails. Je recommande quelques bons articles : l’article d’Andrei Alexandrescu « Ici » et les articles de Hans Boehm « Ici » et « Ici ». Tout le monde, allez le lire vous-même.
◇À propos des opérations atomiques
Certains étudiants savent seulement que les écritures concurrentes par plusieurs threads nécessitent un verrouillage, mais ils ne savent pas que les lectures multiples et les écritures uniques doivent également être protégées. Par exemple, il existe un entier int nCount = 0x01020304 ; dans l'état concurrent, un thread d'écriture modifie sa valeur nCount = 0x05060708 ; un autre thread de lecture obtient la valeur ; Alors, est-il possible que le thread de lecture lise des données « mauvaises » (telles que 0x05060304) ?
Le fait que les données soient corrompues ou non dépend du fait que la lecture et l'écriture de nCount soient des opérations atomiques. Cela dépend de nombreux facteurs liés au matériel (notamment le type de processeur, la longueur des mots du processeur, le nombre d'octets d'alignement mémoire, etc.). Dans certains cas, une corruption des données peut effectivement se produire.
Puisque nous parlons de développement multiplateforme, Dieu sait sur quel type d'environnement matériel votre code sera exécuté à l'avenir. Par conséquent, lorsque vous faites face à des problèmes similaires, vous devez toujours utiliser les classes/fonctions d'opération atomique fournies par des bibliothèques tierces (telles que Atomic_Op d'ACE) pour garantir la sécurité.
◇À propos de la destruction d'objets
Dans la série précédente d'articles "Comment meurent les objets C++ ?", le problème de la mort non naturelle des threads sous la plateforme Win32 et la plateforme Posix a été introduit respectivement.
Étant donné que la couche inférieure des bibliothèques de threads multiplateformes mentionnées ci-dessus doit toujours appeler l'API de thread fournie avec le système d'exploitation, chacun doit toujours faire de son mieux pour s'assurer que tous les threads peuvent mourir naturellement.
Recommandations associées :
Comparaison des similitudes et des différences entre Python et Ruby
Introduction au langage Java (disposition des nœuds de puissance )
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!