iX 9/2018
S. 84
Report
Programmiersprachen
Aufmacherbild

C++-Compiler im Überblick

Detailfragen

Aktuelle Versionen der verschiedenen C++-Compiler unterscheiden sich unter anderem darin, in welchem Umfang sie C++17 unterstützen. Wer einen Umstieg oder ein Upgraden erwägt, kann mit Online-Compilern prüfen, ob sich der Schritt lohnt.

Die besinnlichen Zeiten in der C++-Welt sind endgültig vorbei, die Halbwertszeit von C++-Wissen wird deutlich reduziert. Zwischen den zwei Sprachstandards C++98 und C++11 lag eine (gefühlte) Ewigkeit von 13 Jahren. Dieser Takt ist seit 2011 auf drei Jahre verkürzt. Das C++-Standardisierungskomitee setzte den neuen Rhythmus mit C++11, C++14, C++17 und C++20 mit viel Elan um.

Da stellt sich natürlich die Frage: Wie lässt sich der Wandel von den besinnlichen in diese unruhigen Zeiten bewältigen? Die Antwort ist einfach: Das Netz ist dein Freund. Dieser Artikel gibt einen Überblick über die bekanntesten (Online-)Compiler und welchen Mehrwert sie liefern. Hier geht es insbesondere auch darum, welcher Compiler welchen C++-Standard unterstützt.

Die großen Drei und C++11, C++14 und C++17

Seit 2011 verabschiedete das Standardisierungskomitee in kurzen Abständen von drei Jahren neue Versionen von C++ (Abb. 1).

Das geht ja gut los. Der Artikel schränkt erst mal seinen Fokus auf die populären Compiler GCC, Clang und MSVC und auf die aktuellen Standards C++11, C++14 und C++17 ein (zu Quellen im Web zu den hier besprochenen Tools siehe ix.de/ix1809084). Beides bedeutet aber keine wirkliche Einschränkung. Zum einen setzen wohl mindestens 95 Prozent der Anwender die „großen Drei“ GCC, Clang und MSVC ein. Zum anderen soll dieser Artikel weder Archäologie mit C++98 noch Futurologie mit C++20 betreiben. Der Zeitstrahl in Abbildung 1 gibt einen Überblick über die bestehenden Standards. Nur der Vollständigkeit halber: C++03, als Bugfix zur C++98 im Jahr 2003 verabschiedet, ist aufgrund seiner fehlenden Relevanz nicht Gegenstand dieses Artikels.

Wer spricht welche Sprache?

Tabelle
Tabelle: C++-Standard-Support der großen Drei

Vor der Entscheidung für einen bestimmten Compiler sollten C++-Programmierer sich darüber informieren, welche Version des Compilers welchen C++-Standard unterstützt. Die Tabelle „C++-Standard-Support der großen Drei“ zeigt, ab welcher Compiler-Version GCC, Clang und MSVC jeweils welche Version des Standards bedienen. Dabei bezieht sich die Tabelle im Wesentlichen auf die C++-Kernsprache.

Der MSVC benötigt noch ein paar Erläuterungen. Die Version 19.0 ist Bestandteil von Microsofts Visual Studio 2015, MSVC von Visual Studio 2017. MSVC 19.1 unterstützt teilweise C++17. Dies setzt aber das dritte Update voraus. Höhere Versionen dieses Compilers wie 19.12 oder 19.14 verlangen weitere Updates.

Ins Detail listet cppreference.com auf, welche Features von C++17 die großen Drei unterstützen (Abb. 2). Quelle: en.cppreference.com/w/cpp/compiler_support:
ThreadSanitizer entdeckt das Data Race im Pingpong-Beispiel schon nach den ersten Ballwechseln (Abb. 3).

Alle Details zu C++17 bringt der Screenshot in Abbildung 2 nochmals auf den Punkt. Diejenigen, denen die Informationen zu den großen Drei hier nicht ausreichen und die sich auch für andere C++-Compiler auf weiteren Unix-Plattformen interessieren, finden auf cppreference.com alle Details.

Der Stand der C++-Standardbibliothek variiert ein wenig. Insbesondere gibt es zum jetzigen Zeitpunkt noch keine Implementierung der parallelen STL in C++17. Als einziger der großen Drei bietet der Microsoft-Compiler partielle Unterstützung an (siehe dazu den Beitrag im „Visual C++ Team Blog“ über ix.de/ix1809084). Hier gibt es aber Abhilfe, die ein wenig Bastelarbeit erfordert. Die HPX-Bibliothek (High Performance ParalleX), ein Framework für parallele und verteilte Applikationen, hat bereits die parallele STL implementiert.

Optimierungen durch Compiler-Flags

Damit GCC und Clang den richtigen C++-Standard verwenden können, muss dessen Umsetzung für die Compiler spezifiziert werden. GCC und Clang unterstützen die gleichen Flags. So übersetzt der Aufruf g++ -std=c++11 dataRace.cpp die Sourcecode-Datei dataRace.cpp gemäß dem C++11-Standard. Wird statt c++11 die Angabe c++14 oder c++17 verwendet, kommt ein aktuellerer Standard zum Einsatz. Die Angabe der C++-Standards ist in neueren Versionen des GCC und des Clang-Compilers unter Umständen nicht mehr nötig, schadet aber auch nicht. MSVC benötigt keine Spezifikation des Standards.

Listing 1: Ein Data Race

 1 #include <condition_variable>
 2 #include <iostream>
 3 #include <thread>
 4
 5 bool dataReady= false;
 6
 7 std::mutex mutex_;
 8 std::condition_variable condVar1;
 9 std::condition_variable condVar2;
10 
11 int counter=0;
12 int COUNTLIMIT=50;
13 
14 void setTrue(){
15 
16   while(counter <= COUNTLIMIT){
17 
18     std::unique_lock<std::mutex> lck(mutex_);
19     condVar1.wait(lck,[]{return dataReady == false;});
20     dataReady= true;
21     ++counter;
22     std::cout << dataReady << std::endl;
23     condVar2.notify_one();
24 
25   }
26 }
27 
28 void setFalse(){
29 
30   while(counter < COUNTLIMIT){
31
32     std::unique_lock<std::mutex> lck(mutex_);
33     condVar2.wait(lck,[]{return dataReady == true;});
34     dataReady= false;
35     std::cout << dataReady << std::endl;
36     condVar1.notify_one();
37 
38   }
39 
40 }
41 
42 int main(){
43 
44   std::cout << std::boolalpha << std::endl;
45 
46   std::cout << "Begin: " << dataReady << std::endl;
47 
48   std::thread t1(setTrue);
49   std::thread t2(setFalse);
50 
51   t1.join();
52   t2.join();
53   
54   dataReady= false;
55   std::cout << "End: " << dataReady << std::endl;
56 
57   std::cout << std::endl;
58   
59 }

Es gibt noch viele weitere interessante Compiler-Flags. Die Angaben -O3 (GCC und Clang) und /Ox (MSVC) erzeugen ein maximal optimiertes Programm. -Wall (GCC) beziehungsweise -Wall setzen den maximalen Warnungslevel. Interessant ist der Sanitizer, der seit GCC 4.8 und Clang 3.2 zur Verfügung steht. MSVC-Benutzer müssen mit einer Windows-Portierung ihr Glück versuchen. Sanitizer prüft, ob ein Programm seine Adressen, Threads und den Speicher ordnungsgemäß verwendet. Ein kleines Beispiel soll die Mächtigkeit des ThreadSanitizer unterstreichen. Das Programm in Listing 1 hat ein Data Race, da es auf die Variable counter gleichzeitig lesend und schreibend zugreift.

Das Programm simuliert ein Pingpong-Spiel mithilfe von Bedingungsvariablen. Der Thread t1 führt die Funktion setTrue (Zeile 48) aus, der Thread t2 die Funktion setFalse (Zeile 49). Dabei setzt t1 die geteilte Variable dataReady in Zeile 20 auf true, während t2 die gleiche Variable in Zeile 34 auf false setzt.