MSVC's verbockter C11-Support

Aus meiner Sicht sollte jedes OS und jeder Compiler so funktionieren:

Die wichtigsten OS Features sind durch die C Standard Bibliothek abgebildet. Und die C++ Implementierung baut genau auf diesen C Standard auf.

Microsoft hingegen pfiff bis 2020 auf den C Standard und baute langsamst nur C++11 und C++14 ein.

Doch tief versteckt im Wald, findet man seit 2017 den Header thr/xthreads.h


Eine Geschichte des Verschweigens

Threads, Mutexe, Condition-Variablen und Thread-Local-Storage sind Teil des C11 Standards.
Der kam damals (2011) auch schon 10 Jahre zu spät, denn eigentlich gibt es keine Entschuldigung dafür, dass nach dem Jahr 2000 auch nur eine einzige Sprachvariante auf Threads verzichten musste.

Win32 Threads und POSIX Threads sind seit Mitte der 90er spezifiziert, und Microsofts fehlende Condition-Variablen wurden seit den 2000ern mit Workarounds ersetzt.
Es ist also schon lange alles da, was man zu einem übergreifenden Standard vereinen hätte können.

Doch da Microsoft den C-Teil seines C++ Compilers auf dem Stand von 1989 belassen hatte (nicht mal C99 war unterstützt) und nur auf C++ setzte, hätte diese Story auch hier enden können.

Mit dem MSVC 2013 setzte ein Umdenken ein und plötzlich waren C99 Codes halbwegs fehlerfrei kompilierbar, was die Kernsprache betraf.
Ich bemerkte das daran, dass libReSSL plötzlich kompilierbar wurde, welche vom MSVC 2012 abgelehnt worden war.
Endlich konnte ich von OpenSSL zur libReSSL wechseln.

thrd, mtx und cnd per define

Spätestens 2015 hätte dann meines Erachtens thrd_create, mtx_lock und cnd_signal auch bei Microsoft ihren Siegeszug antreten können, doch aus irgend einem seltsamen Grund, wurde das abgewendet und in den Hintergrund gedrängt.

Denn in MSVC 2015 und MSVC 2017 existiert der Header thr/xthreads.h inklusive CRT Implementierung, der zwar nicht den C11 Standard umsetzt, aber fast ausschließlich aus C11-ähnlichen Symbolen besteht.

Tatsächlich fehlen nur noch ein paar Makros, um aus den xthreads vernünftige C11 Funktionen zu machen.

Und genau das findet auch im GATE Projekt statt, wenn man dieses mit der C11-Option baut:

 1#include <thr/xthreads.h>
 2
 3#define thrd_t       _Thrd_t
 4#define thrd_start_t _Thrd_start_t
 5
 6#define thrd_create(thr, fun, arg) _Thrd_create(thr, fun, arg)
 7#define thrd_detach(thr)           _Thrd_detach(thr)
 8#define thrd_exit(code)            _Thrd_exit(code)
 9#define thrd_join(thr, res)        _Thrd_join(thr, res)
10#define thrd_sleep(tm)             _Thrd_sleep(tm)
11#define thrd_yield                 _Thrd_yield
12#define thrd_equal(thr0, thr1)     _Thrd_equal(thr0, thr1)
13#define thrd_current               _Thrd_current
14
15#define thrd_success _Thrd_success
16#define thrd_nomem   _Thrd_nomem
17#define thrd_timeout _Thrd_timedout
18#define thrd_busy    _Thrd_busy
19#define thrd_error   _Thrd_error
20
21#define mtx_t                       _Mtx_t
22#define mtx_init(ptr_mtx, mtx_type) _Mtx_init(ptr_mtx, mtx_type)
23#define mtx_destroy(ptr_mtx)        _Mtx_destroy(*ptr_mtx)
24#define mtx_lock(ptr_mtx)           _Mtx_lock(*ptr_mtx)
25#define mtx_unlock(ptr_mtx)         _Mtx_unlock(*ptr_mtx)
26#define mtx_trylock(ptr_mtx)        _Mtx_trylock(*ptr_mtx)
27
28#define mtx_plain     _Mtx_plain
29#define mtx_timed     _Mtx_timed
30#define mtx_recursive _Mtx_recursive
31
32#define cnd_t                      _Cnd_t
33#define cnd_init(ptr_cnd)          _Cnd_init(ptr_cnd)
34#define cnd_destroy(ptr_cnd)       _Cnd_destroy(*ptr_cnd)
35#define cnd_signal(ptr_cnd)        _Cnd_signal(*ptr_cnd)
36#define cnd_broadcast(ptr_cnd)     _Cnd_broadcast(*ptr_cnd)
37#define cnd_wait(ptr_cnd, ptr_mtx) _Cnd_wait(*ptr_cnd, *ptr_mtx)
38#define cnd_timedwait(ptr_cnd, ptr_mtx, ptr_timespec) \
39  _Cnd_timedwait(*ptr_cnd, *ptr_mtx, (xtime const*)ptr_timespec)

Nachdem diese Ähnlichkeiten also schon so groß sind, frage ich mich, was wohl der Grund war, diese nützliche C-Schicht im Hintergrund zu verstecken.

Absolut unverzeihlich ist aber das, was im MSVC 2019 und 2022 dann gemacht wurde. Denn hier hat Microsoft den Header thr/xthreads.h entfernt und durch den kürzeren Pfad xthreads.h ersetzt.

Dieser neue xthreads.h setzt (aus meiner Sicht vollkommen unverständlich) C++ Vokabel ein und schließt damit die Nutzung von reinem C aus.
Es handelt sich um die exakt gleichen C Funktionen _Thrd_*, _Mtx_*, _Cnd_*, doch weil irgend ein Vollhonk ein paar typedefs durch using ersetzt hat und alles mit dem namespace std einleitet, wurde die C Kompatibilität sinnlos zerstört.

Noch abstruser ist, dass im Dev-Blog angekündigt wurde, dass der C11-Library Support in Arbeit ist, doch leider bis zum MSVC 2022 Release noch nicht fertig war.

Wie konnte man das also so verbocken, dass man 2015 quasi schon fertig war (siehe #define Beispiel), und 2019 dann alles mit wenigen C++ Phrasen zerstört hat ???

Eine ähnliche Katastrophe ereignete sich auch bei stdatomic.h, das es 2015 auch schon als xatomic.h gab. Doch dort lag von Anfang an alles im C++ Namespace, wobei die Routinen trotzdem die meisten C11 Atomic-Funktionen nachbilden.

Fazit

Im GATE Framework werden letztendlich alle diese Wirrungen der vergangenen Dekaden sichtbar. Nämlich wenn ich C11 cnd_t Typen nutze um Semaphoren nachzubauen und parallel dazu im NT4 Layer mit Win32 Semaphoren Condition-Variablen simuliert werden.

Hätte es bereits gegen 2003 einen Standard wie C11 gegeben und wäre Microsoft hierbei fitter gewesen, hätten wir (in einer idealen Welt) bereits 2005 mit standardisierten C-Threads arbeiten können.
Viele unnötige Workarounds wären uns erspart geblieben und C++ hätte seine std::threads auf einem stabilen (und leichtgewichtigen) C-Fundament aufbauen können.

Ich bezweifle, dass (außer mir) noch andere Entwickler multi-threaded Standard-C Codes in Windows einsetzen. Und das ist mehr als schade, denn die effizientesten Webserver und Sprachcompiler liegen bis heute in reinem C Code vor.

Tja … heute heißt es also:

Früher hätte alles besser werden können.