Übersicht über den neuen MSVC-PräprozessorMSVC new preprocessor overview

Visual Studio 2015 verwendet den herkömmlichen Präprozessor, der nicht mit den Standard mäßigen C++ oder C99 übereinstimmt.Visual Studio 2015 uses the traditional preprocessor, which doesn't conform with Standard C++ or C99. Ab Visual Studio 2019, Version 16,5, ist die neue präprozessorunterstützung für den c++ 20-Standard Feature-Complete.Starting in Visual Studio 2019 version 16.5, new preprocessor support for the C++20 standard is feature-complete. Diese Änderungen sind über den /Zc: präprocessor -Compilerschalter verfügbar.These changes are available by using the /Zc:preprocessor compiler switch. Eine experimentelle Version des neuen Präprozessors ist ab Visual Studio 2017, Version 15,8 und höher, mit dem /experimental: Präprozessor Compiler-Schalter verfügbar.An experimental version of the new preprocessor is available starting in Visual Studio 2017 version 15.8 and later by using the /experimental:preprocessor compiler switch. Weitere Informationen zur Verwendung des neuen Präprozessors in Visual Studio 2017 und Visual Studio 2019 sind verfügbar.More information about using the new preprocessor in Visual Studio 2017 and Visual Studio 2019 is available. Um die Dokumentation für Ihre bevorzugte Version von Visual Studio anzuzeigen, verwenden Sie das Auswahlsteuerelement Version .To see the documentation for your preferred version of Visual Studio, use the Version selector control. Es befindet sich am Anfang des Inhaltsverzeichnisses auf dieser Seite.It's found at the top of the table of contents on this page.

Wir aktualisieren den Microsoft C++-Präprozessor, um die Einhaltung von Standards zu verbessern, langjährige Fehler zu beheben und einige Verhaltensweisen zu ändern, die offiziell nicht definiert sind.We're updating the Microsoft C++ preprocessor to improve standards conformance, fix longstanding bugs, and change some behaviors that are officially undefined. Wir haben auch neue Diagnose hinzugefügt, um bei Fehlern in Makro Definitionen zu warnen.We've also added new diagnostics to warn on errors in macro definitions.

Ab Visual Studio 2019 Version 16,5 ist die präprozessorunterstützung für den c++ 20-Standard Features-Complete.Starting in Visual Studio 2019 version 16.5, preprocessor support for the C++20 standard is feature-complete. Diese Änderungen sind über den /Zc: präprocessor -Compilerschalter verfügbar.These changes are available by using the /Zc:preprocessor compiler switch. Eine experimentelle Version des neuen Präprozessors steht in früheren Versionen ab Visual Studio 2017, Version 15,8, zur Verfügung.An experimental version of the new preprocessor is available in earlier versions starting in Visual Studio 2017 version 15.8. Sie können ihn mithilfe des /experimental: präprocessor -Compilerschalters aktivieren.You can enable it by using the /experimental:preprocessor compiler switch. Das standardmäßige präprozessorverhalten bleibt mit dem in früheren Versionen identisch.The default preprocessor behavior remains the same as in previous versions.

Neues vordefiniertes MakroNew predefined macro

Sie können erkennen, welcher Präprozessor zum Zeitpunkt der Kompilierung verwendet wird.You can detect which preprocessor is in use at compile time. Überprüfen Sie den Wert des vordefinierten Makros _MSVC_TRADITIONAL , um zu ermitteln, ob der herkömmliche Präprozessor bereits verwendet wird.Check the value of the predefined macro _MSVC_TRADITIONAL to tell if the traditional preprocessor is in use. Dieses Makro wird von Versionen des Compilers, die es unterstützen, bedingungslos festgelegt, unabhängig davon, welcher Präprozessor aufgerufen wird.This macro is set unconditionally by versions of the compiler that support it, independent of which preprocessor is invoked. Der Wert für den herkömmlichen Präprozessor ist 1.Its value is 1 for the traditional preprocessor. Der Wert ist 0 (null) für den konformen Präprozessor.It's 0 for the conforming preprocessor.

#if defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL
// Logic using the traditional preprocessor
#else
// Logic using cross-platform compatible preprocessor
#endif

Verhaltensänderungen im neuen PräprozessorBehavior changes in the new preprocessor

Der erste Aufwand für den neuen Präprozessor liegt darin, alle Makro Erweiterungen dem Standard zu entsprechen.The initial work on the new preprocessor has been focused on making all macro expansions conform to the standard. Sie können den MSVC-Compiler mit Bibliotheken verwenden, die zurzeit durch das herkömmliche Verhalten blockiert werden.It lets you use the MSVC compiler with libraries that are currently blocked by the traditional behaviors. Wir haben den aktualisierten Präprozessor in realen Projekten getestet.We tested the updated preprocessor on real world projects. Im folgenden finden Sie einige der gängigeren wichtigen Änderungen, die wir gefunden haben:Here are some of the more common breaking changes we found:

Makro KommentareMacro comments

Der herkömmliche Präprozessor basiert auf Zeichen Puffern anstelle von Präprozessortoken.The traditional preprocessor is based on character buffers rather than preprocessor tokens. Dies ermöglicht ungewöhnliche Verhaltensweisen, wie z. b. den folgenden präprozessorkommentartrick, der unter dem entsprechenden Präprozessor nicht funktioniert:It allows unusual behavior such as the following preprocessor comment trick, which doesn't work under the conforming preprocessor:

#if DISAPPEAR
#define DISAPPEARING_TYPE /##/
#else
#define DISAPPEARING_TYPE int
#endif

// myVal disappears when DISAPPEARING_TYPE is turned into a comment
DISAPPEARING_TYPE myVal;

Die standardkonforme Lösung besteht darin, int myVal innerhalb der entsprechenden Direktiven zu deklarieren #ifdef/#endif :The standards-conforming fix is to declare int myVal inside the appropriate #ifdef/#endif directives:

#define MYVAL 1

#ifdef MYVAL
int myVal;
#endif

L # ValL#val

Der herkömmliche Präprozessor kombiniert fälschlicherweise ein Zeichen folgen Präfix mit dem Ergebnis des Zeichen folgen Operator (#) :The traditional preprocessor incorrectly combines a string prefix to the result of the stringizing operator (#) operator:

 #define DEBUG_INFO(val) L"debug prefix:" L#val
//                                       ^
//                                       this prefix

const wchar_t *info = DEBUG_INFO(hello world);

In diesem Fall ist das L Präfix nicht erforderlich, da die angrenzenden Zeichenfolgenliterale nach der Makro Erweiterung immer kombiniert werden.In this case, the L prefix is unnecessary because the adjacent string literals are combined after macro expansion anyway. Die abwärts kompatible Lösung besteht darin, die Definition zu ändern:The backward-compatible fix is to change the definition:

#define DEBUG_INFO(val) L"debug prefix:" #val
//                                       ^
//                                       no prefix

Das gleiche Problem ist auch bei der Handhabung von Makros zu finden, die das Argument für ein breites Zeichenfolgenliteralzeichen "stringisieren":The same issue is also found in convenience macros that "stringize" the argument to a wide string literal:

 // The traditional preprocessor creates a single wide string literal token
#define STRING(str) L#str

Sie können das Problem auf verschiedene Arten beheben:You can fix the issue in various ways:

  • Verwenden Sie die Zeichen folgen Verkettung von L"" und #str , um Präfix hinzuzufügen.Use string concatenation of L"" and #str to add prefix. Angrenzende Zeichen folgen Literale werden nach der Makro Erweiterung kombiniert:Adjacent string literals are combined after macro expansion:

    #define STRING1(str) L""#str
    
  • Präfix hinzufügen, nachdem der eine #str zusätzliche Makro Erweiterung zugeordnet wurdeAdd the prefix after #str is stringized with additional macro expansion

    #define WIDE(str) L##str
    #define STRING2(str) WIDE(#str)
    
  • Verwenden Sie den Verkettungs Operator ## , um die Token zu kombinieren.Use the concatenation operator ## to combine the tokens. Die Reihenfolge der Vorgänge für ## und # ist nicht angegeben, obwohl alle Compiler den # Operator vor ## in diesem Fall auswerten.The order of operations for ## and # is unspecified, although all compilers seem to evaluate the # operator before ## in this case.

    #define STRING3(str) L## #str
    

Warnung bei ungültigem ##Warning on invalid ##

Wenn der tokeneinfügenden Operator (# #) nicht zu einem einzelnen gültigen Vorverarbeitungs Token führt, ist das Verhalten nicht definiert.When the token-pasting operator (##) doesn't result in a single valid preprocessing token, the behavior is undefined. Der herkömmliche präprozessorvorgang kann die Token nicht kombinieren.The traditional preprocessor silently fails to combine the tokens. Der neue Präprozessor entspricht dem Verhalten der meisten anderen Compiler und gibt eine Diagnose aus.The new preprocessor matches the behavior of most other compilers and emits a diagnostic.

// The ## is unnecessary and does not result in a single preprocessing token.
#define ADD_STD(x) std::##x
// Declare a std::string
ADD_STD(string) s;

Komma-elisions in Variadic-MakrosComma elision in variadic macros

Der herkömmliche MSVC-Präprozessor entfernt vor leeren Ersetzungen immer Kommas __VA_ARGS__ .The traditional MSVC preprocessor always removes commas before empty __VA_ARGS__ replacements. Der neue Präprozessor folgt genauer an das Verhalten anderer beliebter plattformübergreifender Compiler.The new preprocessor more closely follows the behavior of other popular cross-platform compilers. Damit das Komma entfernt wird, muss das Variadic-Argument fehlen (nicht nur leer), und es muss mit einem-Operator gekennzeichnet werden ## .For the comma to be removed, the variadic argument must be missing (not just empty) and it must be marked with a ## operator. Betrachten Sie das folgenden Beispiel:Consider the following example:

void func(int, int = 2, int = 3);
// This macro replacement list has a comma followed by __VA_ARGS__
#define FUNC(a, ...) func(a, __VA_ARGS__)
int main()
{
    // In the traditional preprocessor, the
    // following macro is replaced with:
    // func(10,20,30)
    FUNC(10, 20, 30);

    // A conforming preprocessor replaces the
    // following macro with: func(1, ), which
    // results in a syntax error.
    FUNC(1, );
}

Im folgenden Beispiel fehlt im Aufruf von FUNC2(1) das Variadic-Argument in dem aufgerufenen Makro.In the following example, in the call to FUNC2(1) the variadic argument is missing in the macro being invoked. Im-Rückruf FUNC2(1, ) ist das Variadic-Argument leer, aber nicht vorhanden (Beachten Sie das Komma in der Argumentliste).In the call to FUNC2(1, ) the variadic argument is empty, but not missing (notice the comma in the argument list).

#define FUNC2(a, ...) func(a , ## __VA_ARGS__)
int main()
{
   // Expands to func(1)
   FUNC2(1);

   // Expands to func(1, )
   FUNC2(1, );
}

Im bevorstehenden c++ 20-Standard wurde dieses Problem durch Hinzufügen von gelöst __VA_OPT__ .In the upcoming C++20 standard, this issue has been addressed by adding __VA_OPT__. Die neue präprozessorunterstützung für __VA_OPT__ ist ab Visual Studio 2019, Version 16,5, verfügbar.New preprocessor support for __VA_OPT__ is available starting in Visual Studio 2019 version 16.5.

C++ 20 Variadic-Makro ErweiterungC++20 variadic macro extension

Der neue Präprozessor unterstützt c++ 20 Variadic-Makro Argument Elision:The new preprocessor supports C++20 variadic macro argument elision:

#define FUNC(a, ...) __VA_ARGS__ + a
int main()
  {
  int ret = FUNC(0);
  return ret;
  }

Dieser Code entspricht nicht vor dem c++ 20-Standard.This code isn't conforming before the C++20 standard. In MSVC erweitert der neue Präprozessor dieses c++ 20-Verhalten in niedrigere sprach Standardmodi ( /std:c++14 , /std:c++17 ).In MSVC, the new preprocessor extends this C++20 behavior to lower language standard modes ( /std:c++14 , /std:c++17 ). Diese Erweiterung entspricht dem Verhalten anderer wichtiger plattformübergreifender C++-Compiler.This extension matches the behavior of other major cross-platform C++ compilers.

Makro Argumente werden "entpackt"Macro arguments are "unpacked"

Wenn ein Makro im herkömmlichen Präprozessor eines seiner Argumente an ein anderes abhängiges Makro weiterleitet, wird das Argument beim Einfügen nicht "entpackt".In the traditional preprocessor, if a macro forwards one of its arguments to another dependent macro then the argument doesn't get "unpacked" when it's inserted. Diese Optimierung wird normalerweise nicht bemerkt, kann jedoch zu ungewöhnlichen Verhalten führen:Usually this optimization goes unnoticed, but it can lead to unusual behavior:

// Create a string out of the first argument, and the rest of the arguments.
#define TWO_STRINGS( first, ... ) #first, #__VA_ARGS__
#define A( ... ) TWO_STRINGS(__VA_ARGS__)
const char* c[2] = { A(1, 2) };

// Conforming preprocessor results:
// const char c[2] = { "1", "2" };

// Traditional preprocessor results, all arguments are in the first string:
// const char c[2] = { "1, 2", };

Beim Erweitern A() leitet der herkömmliche Präprozessor alle in paketierten Argumente __VA_ARGS__ an das erste Argument von TWO_STRINGS weiter, wodurch das Variadic-Argument leer bleibt TWO_STRINGS .When expanding A(), the traditional preprocessor forwards all of the arguments packaged in __VA_ARGS__ to the first argument of TWO_STRINGS, which leaves the variadic argument of TWO_STRINGS empty. Dies bewirkt, dass das Ergebnis von #first "1, 2" und nicht nur "1" ist.That causes the result of #first to be "1, 2" rather than just "1". Wenn Sie genau daran arbeiten, Fragen Sie sich vielleicht, was mit dem Ergebnis von #__VA_ARGS__ in der herkömmlichen PräprozessorErweiterung passiert ist: Wenn der Variadic-Parameter leer ist, sollte er zu einem leeren Zeichenfolgenliterals führen "" .If you're following along closely, then you may be wondering what happened to the result of #__VA_ARGS__ in the traditional preprocessor expansion: if the variadic parameter is empty it should result in an empty string literal "". Ein separates Problem hat das Generieren des leeren zeichenfolgenliteraltokens verhindert.A separate issue kept the empty string literal token from being generated.

Die Ersetzungs Liste für Makros wird neu berechnet.Rescanning replacement list for macros

Nachdem ein Makro ersetzt wurde, werden die sich ergebenden Token neu berechnet, um weitere Makro Bezeichner zu ersetzen.After a macro is replaced, the resulting tokens are rescanned for additional macro identifiers to replace. Der Algorithmus, der vom herkömmlichen Präprozessor für die erneute Überprüfung verwendet wird, ist nicht konform, wie in diesem Beispiel basierend auf tatsächlichem Code gezeigt:The algorithm used by the traditional preprocessor for doing the rescan isn't conforming, as shown in this example based on actual code:

#define CAT(a,b) a ## b
#define ECHO(...) __VA_ARGS__
// IMPL1 and IMPL2 are implementation details
#define IMPL1(prefix,value) do_thing_one( prefix, value)
#define IMPL2(prefix,value) do_thing_two( prefix, value)

// MACRO chooses the expansion behavior based on the value passed to macro_switch
#define DO_THING(macro_switch, b) CAT(IMPL, macro_switch) ECHO(( "Hello", b))
DO_THING(1, "World");

// Traditional preprocessor:
// do_thing_one( "Hello", "World");
// Conforming preprocessor:
// IMPL1 ( "Hello","World");

Obwohl dieses Beispiel vielleicht etwas erfunden ist, haben wir es in "Real-World Code" gesehen.Although this example may seem a bit contrived, we've seen it in real-world code.

Um zu sehen, was passiert, können wir die Erweiterung starten mit DO_THING :To see what's going on, we can break down the expansion starting with DO_THING:

  1. DO_THING(1, "World") erweitert zu CAT(IMPL, 1) ECHO(("Hello", "World"))DO_THING(1, "World") expands to CAT(IMPL, 1) ECHO(("Hello", "World"))
  2. CAT(IMPL, 1) erweitert zu IMPL ## 1 , was zu erweitert wird IMPL1CAT(IMPL, 1) expands to IMPL ## 1, which expands to IMPL1
  3. Die Token befinden sich nun in diesem Status: IMPL1 ECHO(("Hello", "World"))Now the tokens are in this state: IMPL1 ECHO(("Hello", "World"))
  4. Der Präprozessor findet den Funktions ähnlichen Makro Bezeichner IMPL1 .The preprocessor finds the function-like macro identifier IMPL1. Da nicht auf folgt ( , wird es nicht als Funktions ähnlicher Makro Aufruf angesehen.Since it's not followed by a (, it isn't considered a function-like macro invocation.
  5. Der Präprozessor wechselt zu den folgenden Token.The preprocessor moves on to the following tokens. Er findet fest, dass das Funktions ähnliche Makro ECHO aufgerufen wird: ECHO(("Hello", "World")) , das zu erweitert wird. ("Hello", "World")It finds the function-like macro ECHO gets invoked: ECHO(("Hello", "World")), which expands to ("Hello", "World")
  6. IMPL1 wird nie für die Erweiterung wieder in Erwägung gezogen, daher lautet das vollständige Ergebnis der Erweiterungen wie folgt: IMPL1("Hello", "World");IMPL1 is never considered again for expansion, so the full result of the expansions is: IMPL1("Hello", "World");

Fügen Sie eine andere Dereferenzierungsschicht hinzu, um das Makro so zu ändern, dass es unter dem neuen Präprozessor und dem herkömmlichen Präprozessor identisch ist:To modify the macro to behave the same way under both the new preprocessor and the traditional preprocessor, add another layer of indirection:

#define CAT(a,b) a##b
#define ECHO(...) __VA_ARGS__
// IMPL1 and IMPL2 are macros implementation details
#define IMPL1(prefix,value) do_thing_one( prefix, value)
#define IMPL2(prefix,value) do_thing_two( prefix, value)
#define CALL(macroName, args) macroName args
#define DO_THING_FIXED(a,b) CALL( CAT(IMPL, a), ECHO(( "Hello",b)))
DO_THING_FIXED(1, "World");

// macro expands to:
// do_thing_one( "Hello", "World");

Unvollständige Features vor 16,5Incomplete features before 16.5

Ab Visual Studio 2019 Version 16,5 ist die neue präprozessorfunktion "Feature-Complete" für c++ 20.Starting in Visual Studio 2019 version 16.5, the new preprocessor is feature-complete for C++20. In früheren Versionen von Visual Studio ist der neue Präprozessor größtenteils fertiggestellt, obwohl einige präprozessordirektivenlogik weiterhin auf das herkömmliche Verhalten zurückgreift.In previous versions of Visual Studio, the new preprocessor is mostly complete, although some preprocessor directive logic still falls back to the traditional behavior. Im folgenden finden Sie eine partielle Liste unvollständiger Features in Visual Studio-Versionen vor 16,5:Here's a partial list of incomplete features in Visual Studio versions before 16.5:

  • Unterstützung für _PragmaSupport for _Pragma
  • C++ 20 FeaturesC++20 features
  • Blockierungs Fehler verstärken: logische Operatoren in präprozessorkonstantenausdrücken sind im neuen Präprozessor vor Version 16,5 nicht vollständig implementiert.Boost blocking bug: Logical operators in preprocessor constant expressions aren't fully implemented in the new preprocessor before version 16.5. Bei einigen #if Direktiven kann der neue Präprozessor auf den herkömmlichen Präprozessor zurückgreifen.On some #if directives, the new preprocessor can fall back to the traditional preprocessor. Der Effekt ist nur sichtbar, wenn Makros, die mit dem herkömmlichen Präprozessor nicht kompatibel sind, erweitert werden.The effect is only noticeable when macros incompatible with the traditional preprocessor get expanded. Dies kann beim Aufbau von Boost-präprozessorslots vorkommen.It can happen when building Boost preprocessor slots.