Heim > Artikel > Backend-Entwicklung > Portabilität und plattformübergreifende Entwicklung von C++ (langer Artikel)
Übersicht
Heute werden wir über die Portabilität von C++ sprechen. Wenn Sie normalerweise C++ für die Entwicklung verwenden und sich über die Portabilitätsprobleme von C++ nicht im Klaren sind, empfehle ich Ihnen, einen Blick auf diese Serie zu werfen. Auch wenn Sie derzeit keinen Bedarf an plattformübergreifender Entwicklung haben, ist es dennoch hilfreich, sich über Portabilität zu informieren.
Das Thema C++-Portabilität ist ein großes Thema und deckt viele Aspekte wie Compiler, Betriebssysteme, Hardwaresysteme usw. ab. Jeder Aspekt hat viel Inhalt. Aufgrund meiner begrenzten Fähigkeiten und Energie kann ich als Referenz nur die häufigsten Probleme in jedem Aspekt vorstellen.
Ich werde es später in Bezug auf Compiler, C++-Syntax, Betriebssystem, Bibliotheken von Drittanbietern, Hilfstools, Entwicklungsprozess usw. vorstellen.
Compiler
Während des plattformübergreifenden Entwicklungsprozesses hängen viele Probleme mit dem Compiler zusammen. Lassen Sie uns zunächst über Compiler-bezogene Probleme sprechen.
Compiler-Auswahl
Zunächst einmal hat die Unterstützung von GCC Priorität, da GCC auf fast allen Betriebssystemplattformen verfügbar ist. Es wird im Grunde ein universeller Compiler. Wenn Ihr Code von GCC auf Plattform A kompiliert und übergeben und dann mit einer ähnlichen Version von GCC auf Plattform B kompiliert werden kann, wird es im Allgemeinen kein großes Problem geben. Daher muss GCC unbedingt darüber nachdenken, es zu unterstützen.
Zweitens überlegen Sie, ob der lokale Compiler unterstützt wird. Der sogenannte lokale Compiler ist der vom Betriebssystemhersteller hergestellte Compiler. Beispiel: Der native Compiler für Windows ist Visual C++. Der lokale Compiler im Vergleich zu Solaris ist SUNs CC. Wenn Sie stärker auf Leistung achten oder die erweiterten Funktionen einiger lokaler Compiler nutzen möchten, müssen Sie möglicherweise die Unterstützung lokaler Compiler sowie von GCC in Betracht ziehen.
Kompilierungswarnungen
Der Compiler ist der Freund des Programmierers. Viele potenzielle Probleme (einschließlich Portabilität) können vom Compiler erkannt und gewarnt werden . Daher empfehle ich dringend:
1. Erhöhen Sie die Warnstufe des Compilers.
2. Ignorieren Sie die Warninformationen des Compilers nicht.
Cross-Compiler
Die Definition von Cross-Compiler finden Sie in „Wikipedia“. Laienhaft ausgedrückt bedeutet es, ein Binärprogramm auf Plattform A zu kompilieren und es auf Plattform B auszuführen. Angenommen, die Anwendung, die Sie entwickeln möchten, läuft unter Solaris, Sie verfügen jedoch nicht über einen SPARC-Rechner, auf dem Solaris ausgeführt werden kann. Zu diesem Zeitpunkt kann ein Cross-Compiler hilfreich sein. Unter normalen Umständen wird GCC zur Erstellung eines Cross-Compilers verwendet. Aus Platzgründen werden wir hier nicht näher darauf eingehen. Interessierte Studierende können sich „hier“ informieren.
Ausnahmebehandlung
Aufgrund des begrenzten Platzes im vorherigen Beitrag „Grammatik“ hatte ich keine Zeit, über Ausnahmen zu sprechen. Jetzt werde ich die Teile, die sich auf Ausnahmen beziehen, herausnehmen und separat darauf eingehen.
Seien Sie vorsichtig, wenn new beim Zuweisen von Speicher fehlschlägt.
Der von frühen alten Compilern generierte Code gibt einen Nullzeiger zurück, wenn new fehlschlägt. Der Borland C++ 3.1, den ich damals verwendet habe, scheint heute so zu sein. Wenn sich der Compiler, den Sie gerade verwenden, immer noch so verhält, liegt ein Problem vor. Sie können erwägen, den neuen Operator zu überladen, um eine bad_alloc-Ausnahme auszulösen, um die Ausnahmebehandlung zu erleichtern.
Ein etwas neuerer Compiler gibt nicht nur einen Nullzeiger zurück. Wenn der neue Operator feststellt, dass Speicher dringend benötigt wird, sollte er laut Standard (siehe Abschnitt 18.4.2 des C++03-Standards) die Funktion new_handler aufrufen (der Prototyp ist typedef void (*new_handler)();). Der Standard empfiehlt, dass die new_handler-Funktion die folgenden drei Dinge ausführt:
1. Versuchen Sie, mehr Speicher zu erhalten.
3 Verfahren.
Da die new_handler-Funktion zurückgesetzt werden kann (durch Aufruf von set_new_handler), kann es zu den oben genannten Verhaltensweisen kommen.
Zusammenfassend gibt es drei Möglichkeiten:
2. Der Prozess wird sofort beendet.
Wenn Sie möchten, dass Ihr Code portabler wird, müssen Sie alle drei Situationen berücksichtigen.
Ausnahmespezifikationen mit Vorsicht verwenden
Meiner Meinung nach sind Ausnahmespezifikationen keine gute Sache. Wenn Sie mir nicht glauben, können Sie Artikel 75 von „C++ Coding Standards – 101 Rules, Guidelines & Best Practices“ lesen. . (Die spezifischen Nachteile werden später in einem separaten Beitrag zur Ausnahme- und Fehlerbehandlung in C++ besprochen.) Näher an der Sache liegt laut Standard (siehe Kapitel 18.6.2 des 03-Standards), wenn eine von einer Funktion ausgelöste Ausnahme nicht enthalten ist in der Ausnahmespezifikation der Funktion, dann sollte unexclused() aufgerufen werden. Allerdings entspricht nicht jeder vom Compiler generierte Code den Standards (z. B. einige Versionen von VC-Compilern). Wenn sich der Compiler, den Sie unterstützen müssen, inkonsistent mit Ausnahmespezifikationen verhält, sollten Sie erwägen, die Ausnahmespezifikationsdeklaration zu entfernen.
Keine Ausnahmen modulübergreifend auslösen
Das hier erwähnte Modul bezieht sich auf die dynamische Bibliothek. Wenn Ihr Programm mehrere dynamische Bibliotheken enthält, lösen Sie keine Ausnahmen außerhalb der exportierten Funktionen des Moduls aus. Schließlich gibt es noch keinen ABI-Standard für C++ (es wird geschätzt, dass es in Zukunft keinen geben wird), und das Auslösen von Ausnahmen über Module hinweg wird zu vielen unvorhersehbaren Verhaltensweisen führen.
Verwenden Sie keine strukturierte Ausnahmebehandlung (SEH)
Wenn Sie noch nie von SEH gehört haben, tun Sie einfach so, als hätte ich es nicht erwähnt und überspringen Sie diesen Absatz. Wenn Sie es gewohnt sind, SEH zu verwenden, müssen Sie diese Gewohnheit ändern, bevor Sie planen, plattformübergreifenden Code zu schreiben. Code, der SEH enthält, kann nur auf der Windows-Plattform kompiliert werden und ist sicherlich nicht plattformübergreifend.
Über Catch(…)
Es liegt auf der Hand, dass die Catch(…)-Anweisung nur C++-Ausnahmetypen erfassen kann und nicht gegen Nicht-C++-Ausnahmen wie Zugriffsverletzungen und Division-durch-Null-Fehler machtlos ist. In einigen Fällen (z. B. bei einigen VC-Compilern) können jedoch auch Zugriffsverletzungen und Division-durch-Null-Fehler von Catch(...) abgefangen werden. Wenn Sie möchten, dass Ihr Code portierbar ist, können Sie sich daher nicht auf das oben beschriebene Catch(...)-Verhalten in Ihrer Programmlogik verlassen.
Bezogen auf das Hardwaresystem
Das Thema, über das wir dieses Mal gesprochen haben, bezieht sich hauptsächlich auf das Hardwaresystem. Wenn Ihr Programm beispielsweise verschiedene CPU-Typen (x86, SPARC, PowerPC) oder denselben CPU-Typ mit unterschiedlichen Wortlängen (z. B. x86 und x86-64) unterstützen muss, müssen Sie sich um das Hardwaresystem kümmern .
Größe der Basistypen
Die Größe (Anzahl der belegten Bytes) der Basistypen in C++ ändert sich, wenn sich die CPU-Wortlänge ändert. Wenn Sie also die Anzahl der von einem int belegten Bytes ausdrücken möchten, schreiben Sie nicht direkt „4“ (das direkte Schreiben von „4“ verstößt übrigens auch gegen das Tabu von Magic Number, Einzelheiten finden Sie hier), sondern sollten schreiben „sizeof(int)“; umgekehrt, wenn Sie eine vorzeichenbehaftete Ganzzahl definieren möchten, deren Größe 4 Bytes betragen muss, verwenden Sie int nicht direkt, sondern einen vordefinierten Typ mit fester Länge (z. B. int32_t der Boost-Bibliothek, ACE). Bibliothek ACE_INT32 usw.).
Ich hätte fast vergessen, dass die Größe des Zeigers auch die oben genannten Probleme mit sich bringt, seien Sie also vorsichtig.
Byte-Reihenfolge
Wenn Sie noch nie von „Byte-Reihenfolge“ gehört haben, lesen Sie bitte „Wikipedia“. Um eine beliebte Analogie zu verwenden: Wenn auf einer Big-Endian-Maschine eine 4-Byte-Ganzzahl 0x01020304 vorhanden ist, wird sie zu 0x04030201, wenn sie über das Netzwerk oder eine Datei an eine Little-Endian-Maschine übertragen wird. Endian-Maschine (Aber ich bin nicht damit in Berührung gekommen), wird die obige Ganzzahl zu 0x02010403.
Wenn die von Ihnen geschriebene Anwendung Netzwerkkommunikation beinhaltet, denken Sie daran, sie zwischen Hostcode und Netzwerkcode zu übersetzen. Wenn es um die Übertragung von Binärdateien zwischen Maschinen geht, denken Sie daran, ähnliche Konvertierungen durchzuführen.
Speicherausrichtung
Wenn Sie nicht wissen, was „Speicherausrichtung“ ist, lesen Sie bitte „Wikipedia“. Vereinfacht gesagt liegen die Daten in der Struktur aus Gründen der CPU-Verarbeitungsleistung nicht nahe beieinander, sondern sollten durch etwas Leerzeichen getrennt sein. In diesem Fall ist die Adresse jedes Datenelements in der Struktur genau ein ganzzahliges Vielfaches einer bestimmten Wortlänge.
Da die Details der Speicherausrichtung nicht im C++-Standard definiert sind, kann sich Ihr Code nicht auf die Details der Ausrichtung verlassen. Überall dort, wo die Größe einer Struktur berechnet wird, wird sizeof() ehrlich geschrieben.
Einige Compiler unterstützen die Vorverarbeitungsanweisung #pragma pack (die zum Ändern der Ausrichtungswortlänge verwendet werden kann. Diese Syntax wird jedoch nicht von allen Compilern unterstützt, daher sollten Sie sie mit Vorsicht verwenden).
Verschiebungsoperation
Für die Rechtsverschiebungsoperation von vorzeichenbehafteten Ganzzahlen verwenden einige Systeme standardmäßig eine arithmetische Rechtsverschiebung (das höchste Vorzeichenbit bleibt unverändert) und einige standardmäßige logische Rechtsverschiebung (das höchste Vorzeichenbit wird mit 0 gefüllt). . Führen Sie daher bei vorzeichenbehafteten Ganzzahlen keine Rechtsverschiebungen durch. Auch wenn es keine Portabilitätsprobleme gibt, sollten Sie übrigens versuchen, in Ihrem Code so wenig Shift-Operatoren wie möglich zu verwenden. Diejenigen Schüler, die versuchen, Schichtoperationen zur Leistungsverbesserung zu nutzen, sollten nicht nur darauf achten, dass dies nicht nur schlecht lesbar ist, sondern auch undankbar ist. Solange der Compiler nicht zu geistig zurückgeblieben ist, übernimmt er diese Optimierung automatisch für Sie, ohne dass sich der Programmierer darum kümmern muss.
Betriebssystem
Im vorherigen Beitrag wurden Themen im Zusammenhang mit „Hardwaresystem“ erwähnt. Heute sprechen wir über Themen im Zusammenhang mit dem Betriebssystem. Es gibt viele triviale Dinge im Zusammenhang mit dem Betriebssystem in der plattformübergreifenden C++-Entwicklung, daher werde ich heute lange ausführlich sein :-)
Um nicht um den heißen Brei herumzureden, werden im Folgenden Linux und Verschiedene Unix-Systeme werden zusammenfassend als Posix-Systeme bezeichnet.
Dateisystem (Dateisystem, im Folgenden als FS bezeichnet)
Die meisten Anfänger, die gerade mit der plattformübergreifenden Entwicklung begonnen haben, werden auf Probleme im Zusammenhang mit FS stoßen. Sprechen wir also zuerst über FS. Zusammenfassend lässt sich sagen, dass zu den FS-Unterschieden, die während der Entwicklung leicht auftreten, hauptsächlich die folgenden gehören: Unterschiede bei den Verzeichnistrennzeichen; Unterschiede bei der Groß- und Kleinschreibung; Unterschiede bei den verbotenen Zeichen in Pfaden.
Um die oben genannten Unterschiede zu bewältigen, sollten Sie auf die folgenden Punkte achten:
1. Die Benennung von Dateien und Verzeichnissen sollte standardisiert sein.
Verwenden Sie bei der Benennung von Dateien und Verzeichnissen nur Buchstaben und Zahlen. Platzieren Sie nicht zwei Dateien mit ähnlichen Namen (nur die Groß-/Kleinschreibung unterscheidet sich bei den Namen, z. B. foo.cpp und Foo.cpp) im selben Verzeichnis. Verwenden Sie einige vom Betriebssystem reservierte Wörter (z. B. aux, con, nul, prn) nicht als Dateinamen oder Verzeichnisnamen.
Hinzu kommt, dass die gerade erwähnte Benennung Quellcodedateien, Binärdateien und andere zur Laufzeit erstellte Dateien umfasst.
2. #include-Anweisungen sollten standardisiert sein
Achten Sie beim Schreiben von #include-Anweisungen darauf, Schrägstriche „/“ (häufiger) anstelle von Backslashes „“ (nur in Windows verfügbar) zu verwenden. Die Datei- und Verzeichnisnamen in der #include-Anweisung müssen genau in der gleichen Groß-/Kleinschreibung angegeben werden wie die tatsächlichen Namen.
3. Wenn der Code FS-Operationen beinhaltet, versuchen Sie, vorgefertigte Bibliotheken zu verwenden
Es gibt bereits viele ausgereifte Bibliotheken von Drittanbietern für FS (z. B. boost::filesystem). Wenn Ihr Code FS-Vorgänge (z. B. Verzeichnisdurchquerung) umfasst, versuchen Sie, diese Bibliotheken von Drittanbietern zu verwenden, was Ihnen viel Arbeit ersparen kann.
★Wagenrücklauf CR/Zeilenvorschub LF für Textdateien
Dieses lästige Problem wird durch die inkonsistente Handhabung von Wagenrücklauf/Zeilenvorschub durch mehrere bekannte Betriebssysteme verursacht. Die aktuelle Situation ist: Windows verwendet sowohl CR als auch LF; die meisten Unix-Geräte verwenden LF; Apples Mac-Serie verwendet CR.
Für die Quellcodeverwaltung können glücklicherweise viele Versionsverwaltungsprogramme (z. B. CVS, SVN) dieses Problem intelligent lösen, sodass Sie lokalen Quellcode aus der Codebasis abrufen und an das lokale Format anpassen können.
Wenn Ihr Programm zur Laufzeit Textdateien verarbeiten muss, beachten Sie bitte den Unterschied zwischen dem Öffnen im Textmodus und dem Öffnen im Binärmodus. Berücksichtigen Sie auch eine angemessene Handhabung, wenn Textdateien zwischen verschiedenen Systemen übertragen werden müssen.
★Dateisuchpfad (einschließlich der Suche nach ausführbaren Dateien und dynamischen Bibliotheken)
Wenn Sie unter Windows eine Datei ausführen oder eine dynamische Bibliothek laden möchten, durchsuchen Sie im Allgemeinen das aktuelle Verzeichnis in Posix-Systemen. Wenn Ihre Anwendung also das Starten von Prozessen oder das Laden dynamischer Bibliotheken umfasst, achten Sie auf diesen Unterschied.
★Umgebungsvariablen
Für das oben erwähnte Suchpfadproblem möchten einige Schüler den aktuellen Pfad einführen, indem sie PATH und LD_LIBRARY_PATH ändern. Wenn Sie diese Methode verwenden, wird empfohlen, nur die Umgebungsvariablen auf Prozessebene und nicht die Umgebungsvariablen auf Systemebene zu ändern (Änderungen auf Systemebene können sich auf andere Software auf demselben Computer auswirken und Nebenwirkungen verursachen).
★Dynamische Bibliothek
Wenn Ihre Anwendung eine dynamische Bibliothek verwendet, wird dringend empfohlen, dass die dynamische Bibliothek Standardfunktionen im C-Stil exportiert (versuchen Sie, keine Klassen zu exportieren). Wenn Sie eine dynamische Bibliothek in ein Posix-System laden, denken Sie daran, das RTLD_GLOBAL-Flag mit Vorsicht zu verwenden. Dieses Flag aktiviert die globale Symboltabelle, was zu Symbolnamenkonflikten zwischen mehreren dynamischen Bibliotheken führen kann (wenn dies geschieht, treten unglaubliche Laufzeitfehler auf, die äußerst schwer zu debuggen sind).
★Service-/Guardian-Prozess
Wenn Sie sich über die Konzepte von Services und Guard-Prozessen nicht im Klaren sind, lesen Sie bitte Wikipedia (hier und hier). Zur Vereinfachung der Beschreibung werden sie im Folgenden gemeinsam als Dienste bezeichnet.
Da es sich bei den meisten in C++ entwickelten Modulen um Hintergrundmodule handelt, treten häufig Serviceprobleme auf. Das Schreiben von Diensten erfordert den Aufruf mehrerer systembezogener APIs, was zu einer engen Kopplung mit dem Betriebssystem führt und die Verwendung eines einzigen Codesatzes erschwert. Daher ist es eine bessere Möglichkeit, eine allgemeine Service-Shell zu abstrahieren und dann den Geschäftslogikcode als dynamische Bibliothek darunter bereitzustellen. In diesem Fall ist mindestens ein Satz Geschäftslogikcode gewährleistet; obwohl zwei Sätze Service-Shell-Code erforderlich sind (einer für Windows und einer für Posix), sind sie geschäftsunabhängig und können problemlos wiederverwendet werden.
★Standard-Stack-Größe
Unterschiedliche Betriebssysteme, die Standard-Stack-Größe variiert stark und reicht von Dutzenden von KB (es wird gesagt, dass Symbian nur 12 KB hat, was wirklich geizig ist) bis zu mehreren MB. Daher sollten Sie sich im Voraus nach der Standard-Stack-Größe des Zielsystems erkundigen. Wenn Sie auf ein geiziges System wie Symbian stoßen, können Sie darüber nachdenken, diese mithilfe von Compiler-Optionen zu erhöhen. Natürlich ist es auch wichtig, die gute Angewohnheit zu entwickeln, „keine großen Arrays/großen Objekte auf dem Stapel zu definieren“, da sonst der Stapel platzt, egal wie groß er ist.
Multi-Threading
Die Beiträge, die ich im letzten Monat oder so geschrieben habe, waren ziemlich kompliziert, was dazu geführt hat, dass diese Serie lange Zeit nicht aktualisiert wurde. Infolgedessen drängte mich ein anderer Internetnutzer in den Kommentaren, was mich ein wenig in Verlegenheit brachte. Beeilen Sie sich und holen Sie sich noch heute das Multithreading-Kapitel. Als ich das letzte Mal über Betriebssysteme gesprochen habe, habe ich über viele verschiedene Themen gesprochen, da betriebssystembezogene Themen relativ trivial waren. Damals bemerkte ich, dass der Artikel etwas lang war, also habe ich die Multiprozess- und Multithreading-Teile auf später verschoben.
★Compiler
◇Über C-Laufzeitbibliotheksoptionen
Lassen Sie uns zunächst über eine sehr grundlegende Frage sprechen: über die Einstellungen der C-Laufzeitbibliothek (im Folgenden als CRT: C Run-Time bezeichnet). Ich wollte nicht über ein so untergeordnetes Thema sprechen, aber es gibt mehrere Menschen um mich herum, die an diesem Ort Verluste erlitten haben, also könnte ich genauso gut darüber sprechen.
Die meisten C++-Compiler werden mit CRT geliefert (vielleicht mehr als eines). Die mit einigen Compilern gelieferte CRT kann je nach Thread-Unterstützung in Single-Thread-CRT und Multi-Thread-CRT unterteilt werden. Wenn Sie eine Multithread-Entwicklung durchführen möchten, vergessen Sie nicht sicherzustellen, dass das entsprechende C++-Projekt ein Multithread-CRT verwendet. Sonst wird es ein hässlicher Tod sein.
Insbesondere wenn Sie Visual C++ zum Erstellen von Ingenieurprojekten verwenden, müssen Sie vorsichtiger sein. Wenn das neu erstellte Projekt kein MFC enthält (einschließlich Konsolenprojekten und Win32-Projekten), ist die Standardeinstellung des Projekts die Verwendung von „Single-Threaded CRT“, wie in der folgenden Abbildung dargestellt:
◇Über Optimierungsoptionen
„Optimierungsoptionen“ sind ein weiteres wichtiges Compiler-bezogenes Thema. Einige Compiler bieten Optimierungsoptionen, die angeblich großartig sind, einige Optimierungsoptionen können jedoch potenzielle Risiken bergen. Der Compiler kann aus eigener Initiative die Ausführungsreihenfolge von Anweisungen stören, was zu unerwarteten Thread-Race-Bedingungen führt (Race Condition, siehe „hier“ für eine detaillierte Erklärung). Liu Weipeng gab mehrere typische Beispiele in „C++ Multithreaded Memory Model“.
Es wird empfohlen, nur die regulären Geschwindigkeitsoptimierungsoptionen des Compilers zu verwenden. Die zusätzlichen Auswirkungen anderer ausgefallener Optimierungsoptionen sind möglicherweise nicht offensichtlich, aber die potenziellen Risiken sind nicht gering. Das Risiko ist es wirklich nicht wert.
Am Beispiel von GCC: Es wird empfohlen, die Option -O2 zu verwenden (tatsächlich ist -O2 eine Sammlung mehrerer Optionen). Es besteht kein Grund, das Risiko einzugehen, -O3 zu verwenden (es sei denn, Sie haben eine sehr gute Option). Grund). Zusätzlich zu -O2 und -O3 verfügt GCC über eine große Anzahl (schätzungsweise Hunderte) weiterer Optimierungsoptionen. Wenn Sie eine der Optionen nutzen möchten, müssen Sie zunächst deren Eigenschaften und mögliche Nebenwirkungen verstehen, sonst wissen Sie in Zukunft nicht mehr, wie Sie sterben sollen.
★Auswahl der Thread-Bibliothek
Da der aktuelle C++03-Standard kaum Thread-bezogene Inhalte beinhaltet (selbst wenn C++0x in Zukunft eine Standardbibliothek für Threads enthält, ist die Unterstützung von Compiler-Herstellern möglicherweise nicht vorhanden). Daher wird die plattformübergreifende Multithreading-Unterstützung noch lange auf Bibliotheken von Drittanbietern angewiesen sein. Daher ist die Wahl der Thread-Bibliothek sehr wichtig. Hier finden Sie eine kurze Einführung in mehrere bekannte plattformübergreifende Thread-Bibliotheken.
◇ACE
Lassen Sie uns zunächst über ACE sprechen, eine Bibliothek mit einer langen Geschichte. Wenn Sie noch nie zuvor darauf gestoßen sind, schauen Sie sich zuerst „hier“ die Lese- und Schreibkompetenz an. Dem vollständigen Namen von ACE (Adaptive Communication Environment) nach zu urteilen, sollte es auf „Kommunikation“ basieren. Allerdings ist die Unterstützung von ACE für das Nebengeschäft „Multithreading“ immer noch sehr umfassend, wie Mutex-Sperren (ACE_Mutex), Bedingungsvariablen (ACE_Condition), Semaphoren (ACE_Semaphore), Barrieren (ACE_Barrier), atomare Operationen (ACE_Atomic_Op) usw . Bestimmte Typen wie ACE_Mutex werden auch in Thread-Lese-/Schreibsperren (ACE_RW_Thread_Mutex), Thread-rekursive Sperren (ACE_Recursive_Thread_Mutex) usw. unterteilt.
Neben der umfassenden Unterstützung hat ACE einen weiteren offensichtlichen Vorteil: Es bietet eine gute Unterstützung für verschiedene Betriebssystemplattformen und ihre eigenen Compiler. Einschließlich einiger altmodischer Compiler (wie VC6) kann es auch unterstützt werden (die hier erwähnte Unterstützung bedeutet nicht nur, dass es kompiliert werden kann, sondern auch stabil ausgeführt werden kann). Dieser Vorteil liegt bei der plattformübergreifenden Entwicklung auf der Hand.
Was sind die Mängel? Da ACE sehr früh gestartet wurde (wahrscheinlich Mitte der 1990er Jahre), waren viele der alten Funktionen von C++ noch nicht veröffentlicht (geschweige denn neue Funktionen), sodass sich der Gesamtstil von ACE altmodisch anfühlte und Boost weit unterlegen war. So modisch und avantgardistisch.
◇boost::thread
boost::thread steht in scharfem Kontrast zu ACE. Dieses Ding scheint seit Boost-Version 1.32 eingeführt worden zu sein und ist jünger als ACE. Dank der Unterstützung einer Expertengruppe von Boost geht die Entwicklung jedoch recht schnell voran. Ab der aktuellen Boost-Version 1.38 kann es auch viele Funktionen unterstützen (scheint aber nicht so viele wie ACE). Angesichts der Tatsache, dass sich im Laufe der Zeit viele Mitglieder des C++-Standardkomitees in der Boost-Community versammeln, wird boost::thread schließlich zum aufsteigenden Stern unter den C++-Threads mit einer glänzenden Zukunft!
Der Nachteil von boost::thread besteht darin, dass es viele Compiler nicht unterstützt, insbesondere einige altmodische Compiler (viele Boost-Unterbibliotheken haben dieses Problem, hauptsächlich weil sie eine erweiterte Vorlagensyntax verwenden). Dies ist ein offensichtliches Problem für plattformübergreifende Plattformen.
◇wxWidgets und QT
wxWidgets und QT sind beide GUI-Schnittstellenbibliotheken, verfügen aber auch über integrierte Unterstützung für Threads. Eine Einführung in wxWidgets-Threads finden Sie „hier“ und eine Einführung in QT-Threads finden Sie „hier“. Diese beiden Bibliotheken unterstützen Threads auf ähnliche Weise und bieten häufig verwendete Mechanismen wie Mutex, Bedingung und Semaphor. Allerdings sind die Funktionen nicht so umfangreich wie bei ACE.
◇Wie man abwägt
Für die Entwicklung von GUI-Software und die Verwendung von wxWidgets oder QT können Sie direkt deren integrierte Thread-Bibliotheken verwenden (vorausgesetzt, Sie verwenden nur grundlegende Thread-Funktionen). Aufgrund ihrer integrierten Thread-Bibliothek sind die Funktionen etwas dürftig. Falls Sie eine erweiterte Threading-Funktionalität benötigen, sollten Sie erwägen, diese durch boost::thread oder ACE zu ersetzen.
Die Wahl zwischen boost::thread und ACE hängt hauptsächlich von den Anforderungen der Software ab. Wenn Sie viele und komplexe Plattformen unterstützen möchten, empfiehlt sich die Verwendung von ACE, um Probleme zu vermeiden, die vom Compiler nicht unterstützt werden. Wenn Sie nur einige Mainstream-Plattformen (wie Windows, Linux, Mac) unterstützen müssen, wird die Verwendung von boost::thread empfohlen. Schließlich unterstützen Compiler auf Mainstream-Betriebssystemen Boost immer noch gut.
★Vorsichtsmaßnahmen bei der Programmierung
Tatsächlich gibt es viele Dinge, auf die bei der Multithread-Entwicklung geachtet werden muss. Ich kann nur einige Dinge auflisten, die beeindruckender sind.
◇Über volatile
Wenn wir über die Fallen sprechen, die bei der Multithread-Programmierung auftreten können, müssen wir das Schlüsselwort volatile erwähnen. Wenn Sie nicht viel darüber wissen, lesen Sie zuerst „hier“, um mehr darüber zu erfahren. Da sowohl der C++ 98- als auch der C++ 03-Standard kein Multithread-Speichermodell definieren, verfügen die Standards nur über ein wenig Volatilität und Threading. Infolgedessen konzentrieren sich viele Speichelleckere in der C++-Community auf Volatile (einschließlich des Speichelleckers vieler C++-Experten). Aus diesem Grund werde ich hier nicht näher darauf eingehen. Ich empfehle ein paar tolle Artikel: Andrei Alexandrescus Artikel „Hier“ und Hans Boehms Artikel „Hier“ und „Hier“. Kommt alle vorbei und lest es selbst.
◇Über atomare Operationen
Einige Schüler wissen nur, dass konkurrierende Schreibvorgänge durch mehrere Threads gesperrt werden müssen, sie wissen jedoch nicht, dass auch mehrere Lesevorgänge und einzelne Schreibvorgänge geschützt werden müssen. Beispielsweise gibt es eine Ganzzahl int nCount = 0x01020304 im gleichzeitigen Zustand, ein schreibender Thread ändert seinen Wert nCount = 0x05060708; Ist es also möglich, dass der Lesethread „schlechte“ Daten (z. B. 0x05060304) liest?
Ob die Daten beschädigt sind oder nicht, hängt davon ab, ob das Lesen und Schreiben von nCount atomare Operationen sind. Dies hängt von vielen hardwarebezogenen Faktoren ab (einschließlich des CPU-Typs, der Wortlänge der CPU, der Anzahl der Bytes der Speicherausrichtung usw.). In einigen Fällen kann es tatsächlich zu Datenbeschädigungen kommen.
Da es sich um plattformübergreifende Entwicklung handelt, weiß Gott, in welcher Hardwareumgebung Ihr Code in Zukunft ausgeführt wird. Daher müssen Sie bei der Lösung ähnlicher Probleme weiterhin die atomaren Operationsklassen/-funktionen verwenden, die von Bibliotheken von Drittanbietern (z. B. Atomic_Op von ACE) bereitgestellt werden, um die Sicherheit zu gewährleisten.
◇Über die Zerstörung von Objekten
In der vorherigen Beitragsreihe „Wie sterben C++-Objekte?“ wurde das Problem des unnatürlichen Todes von Threads unter der Win32-Plattform bzw. der Posix-Plattform vorgestellt.
Da die unterste Ebene der oben genannten plattformübergreifenden Thread-Bibliotheken immer noch die mit dem Betriebssystem gelieferte Thread-API aufrufen muss, muss jeder sein Bestes geben, um sicherzustellen, dass alle Threads auf natürliche Weise sterben können.
Verwandte Empfehlungen:
Vergleich der Ähnlichkeiten und Unterschiede zwischen Python und Ruby
Einführung in die Java-Sprache (Anordnung der Leistungsknoten). )
Das obige ist der detaillierte Inhalt vonPortabilität und plattformübergreifende Entwicklung von C++ (langer Artikel). Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!