MSVC nový preprocesor – přehled
Visual Studio 2015 používá tradiční preprocesor, který není v souladu se standardem C++ nebo C99. 2019 Visual Studio počínaje verzí 16,5 verze je nová podpora preprocesoru pro standard c++ 20 kompletní funkcí. Tyto změny jsou k dispozici pomocí přepínače kompilátoru /Zc: preprocesor . experimentální verze nového preprocesoru je k dispozici od verze Visual Studio 2017 15,8 nebo novější pomocí přepínače kompilátoru /experimental: preprocesor . další informace o použití nového preprocesoru v Visual Studio 2017 a Visual Studio 2019 je k dispozici. chcete-li zobrazit dokumentaci k preferované verzi Visual Studio, použijte ovládací prvek selektor verzí . Nachází se v horní části obsahu na této stránce.
Aktualizujeme preprocesor Microsoft C++ pro zlepšení dodržování standardů, opravu chyb dlouhodobě zavazuje chránit a změnu některých chování, která jsou oficiálně nedefinovaná. Přidali jsme také novou diagnostiku, která upozorňuje na chyby v definicích maker.
od verze Visual Studio 2019 16,5 je podpora preprocesoru pro standard c++ 20 funkce dokončena. Tyto změny jsou k dispozici pomocí přepínače kompilátoru /Zc: preprocesor . experimentální verze nového preprocesoru je k dispozici v dřívějších verzích od verze Visual Studio 2017 15,8. Můžete ji povolit pomocí přepínače kompilátoru /Experimental: preprocesor . Výchozí chování preprocesoru zůstává stejné jako v předchozích verzích.
Nové předdefinované makro
Můžete zjistit, který preprocesor se používá v době kompilace. Ověřte hodnotu předdefinovaného makra _MSVC_TRADITIONAL a sdělte, zda se tradiční preprocesor používá. Toto makro je nastaveno na nepodmíněné verze kompilátoru, který ho podporuje, nezávisle na tom, který preprocesor je vyvolán. Jeho hodnota je 1 pro tradiční preprocesor. Je 0 pro vyhovující preprocesor.
#if defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL
// Logic using the traditional preprocessor
#else
// Logic using cross-platform compatible preprocessor
#endif
Změny chování v novém preprocesoru
Počáteční práce na novém preprocesoru se zaměřuje na to, že všechna rozšíření makra jsou v souladu se standardem. umožňuje použít MSVC kompilátor s knihovnami, které jsou aktuálně blokovány tradičním chováním. Otestovali jsme aktualizované preprocesory na projektech reálného světa. Tady jsou některé z nejběžnějších nejnovějších změn, které jsme našli:
Komentáře makra
Tradiční preprocesor je založen na vyrovnávací paměti znaků spíše než tokeny preprocesoru. Umožňuje neobvyklé chování, jako je například následující štych komentáře preprocesoru, který nefunguje s vyhovujícím preprocesorem:
#if DISAPPEAR
#define DISAPPEARING_TYPE /##/
#else
#define DISAPPEARING_TYPE int
#endif
// myVal disappears when DISAPPEARING_TYPE is turned into a comment
DISAPPEARING_TYPE myVal;
Oprava vyhovující standardům je deklarována int myVal v příslušných #ifdef/#endif direktivách:
#define MYVAL 1
#ifdef MYVAL
int myVal;
#endif
L # Val
Tradiční preprocesor nesprávně kombinuje předponu řetězce s výsledkem operátoru operátoru převodu (#) :
#define DEBUG_INFO(val) L"debug prefix:" L#val
// ^
// this prefix
const wchar_t *info = DEBUG_INFO(hello world);
V tomto případě L je předpona zbytečná, protože sousední řetězcové literály jsou kombinovány po rozšíření makra. Zpětně kompatibilní oprava je změna definice:
#define DEBUG_INFO(val) L"debug prefix:" #val
// ^
// no prefix
Stejný problém je také nalezen v praktických makrech, které "převádějícím" argumentem na řetězec literálu v šířce:
// The traditional preprocessor creates a single wide string literal token
#define STRING(str) L#str
Problém můžete vyřešit různými způsoby:
Použijte zřetězení řetězců
L""a#strk přidání předpony. Sousední řetězcové literály jsou kombinovány po rozšíření makra:#define STRING1(str) L""#strPřidání předpony po
#strřetězce s dalšími rozšířeními makra#define WIDE(str) L##str #define STRING2(str) WIDE(#str)##K kombinování tokenů použijte operátor zřetězení. Pořadí operací pro##a#je neurčeno, i když všechny kompilátory se jeví k vyhodnocení#operátoru před##v tomto případě.#define STRING3(str) L## #str
Upozornění na neplatné ##
Pokud operátor vložení tokenu (# #) nevede k jednomu platnému tokenu předzpracování, chování není definováno. Tradiční preprocesor bez upozornění nemůže kombinovat tokeny. Nový preprocesor se shoduje s chováním většiny ostatních kompilátorů a generuje diagnostiku.
// 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;
Čárka Elizi v makrech variadické
tradiční preprocesor MSVC vždy odebere čárky před prázdné __VA_ARGS__ náhrady. Nový preprocesor se podrobněji řídí chováním ostatních oblíbených kompilátorů pro různé platformy. Aby byla čárka odebrána, argument variadické musí chybět (není to nic prázdné) a musí být označený pomocí ## operátoru. Uvažujte následující příklad:
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, );
}
V následujícím příkladu ve volání FUNC2(1) argumentu variadické chybí ve vyvolaném makru. V volání FUNC2(1, ) argumentu variadické je prázdný, ale chybí (Všimněte si čárky v seznamu argumentů).
#define FUNC2(a, ...) func(a , ## __VA_ARGS__)
int main()
{
// Expands to func(1)
FUNC2(1);
// Expands to func(1, )
FUNC2(1, );
}
V nadcházejícím standardu C++ 20 byl tento problém řešen přidáním __VA_OPT__ . nová podpora preprocesoru pro __VA_OPT__ je dostupná od verze Visual Studio 2019 16,5.
Rozšíření makra c++ 20 variadické
Nový preprocesor podporuje Argument makra C++ 20 variadické Elizi:
#define FUNC(a, ...) __VA_ARGS__ + a
int main()
{
int ret = FUNC(0);
return ret;
}
Tento kód se neshoduje s standardem C++ 20. v MSVC nový preprocesor rozšiřuje toto chování jazyka c++ 20 na nižší standardní režimy ( /std:c++14 , /std:c++17 ). Toto rozšíření se shoduje s chováním dalších hlavních kompilátorů C++ pro různé platformy.
Argumenty makra jsou "unpacked".
V tradičním preprocesoru, pokud makro předává některý z jeho argumentů jinému závislému makru, pak argument nezíská "unpacked" při vložení. Obvykle se tato optimalizace neprojeví, ale může vést k neobvyklému chování:
// 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", };
Při rozšiřování A() tradiční preprocesor předá všechny argumenty v rámci __VA_ARGS__ do prvního argumentu TWO_STRINGS, což ponechá argument variadické TWO_STRINGS prázdný. To způsobí, že výsledkem #first bude "1, 2" místo pouze "1". Pokud vás podržíte úzce, může vás zajímat, co se stalo s výsledkem #__VA_ARGS__ v tradičním rozšíření preprocesoru: Pokud je parametr variadické prázdný, měla by být výsledkem prázdný řetězcový literál "" . Samostatný problém zachová prázdný řetězcový literálový token od vygenerování.
Znovu prohledat náhradní seznam pro makra
Po nahrazení makra se u výsledných tokenů znovu vyhledají další identifikátory maker, které se mají nahradit. Algoritmus používaný tradičním preprocesorem pro provedení opětovného prohledání není v souladu s tímto příkladem, jak je znázorněno v tomto příkladu na základě aktuálního kódu:
#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");
I když se tento příklad může zdát bit contrived, zobrazili jsme ho v reálném světě.
Pokud chcete zjistit, co se právě používá, můžeme rozšířit rozšíření od DO_THING :
DO_THING(1, "World")rozšíří naCAT(IMPL, 1) ECHO(("Hello", "World"))CAT(IMPL, 1)rozšíří naIMPL ## 1, který se rozšíří naIMPL1- Teď jsou tokeny v tomto stavu:
IMPL1 ECHO(("Hello", "World")) - Preprocesor vyhledá identifikátor makra podobného funkci
IMPL1. Vzhledem k tomu, že není následován(, není považován za vyvolání makra jako funkce. - Preprocesor se přesune na následující tokeny. Nalezne vyvolané makro podobné funkci
ECHO:ECHO(("Hello", "World")), které se rozšíří na("Hello", "World") IMPL1se nikdy nepovažuje za rozšíření, takže úplný výsledek rozšíření je:IMPL1("Hello", "World");
Chcete-li upravit makro tak, aby se chovalo stejným způsobem v rámci nového preprocesoru i tradičního preprocesoru, přidejte další vrstvu dereference:
#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");
Neúplné funkce před 16,5
od verze Visual Studio 2019 16,5 je nový preprocesor funkcí dokončený pro c++ 20. v předchozích verzích Visual Studio je nový preprocesor většinou kompletní, i když se některá z těchto direktiv preprocesoru stále nevrátí k tradičnímu chování. tady je částečný seznam neúplných funkcí v Visual Studio verzích před 16,5:
- Podpora pro
_Pragma - Funkce c++ 20
- Zesílení blokující chybu: logické operátory v konstantních výrazech preprocesoru nejsou plně implementovány v novém preprocesoru před verzí 16,5. U některých
#ifdirektiv se může nový preprocesor vrátit k tradičnímu preprocesoru. Tento efekt je patrné pouze v případě, že jsou makra nekompatibilní s tradičním preprocesorem rozbalena. K tomu může dojít při sestavování, když se budou zvyšovat sloty preprocesoru.