MSVC yeni ön işlemciye genel bakış
Visual Studio 2015, Standart C++ veya C99 ile uyumlu olmayan geleneksel ön işlemciyi kullanır. Visual Studio 2019 sürüm 16.5'den başlayarak, C++20 standardı için yeni ön işlemci desteği özellik tamamlanır. Bu değişiklikler /Zc:önişlemci derleyici anahtarı kullanılarak kullanılabilir. Visual Studio 2017 sürüm 15.8 ve sonraki sürümlerden başlayarak yeni ön işlemcinin deneysel bir sürümü /experimental:preprocessor derleyici anahtarı kullanılarak kullanılabilir. Visual Studio 2017 ve Visual Studio 2019'da yeni ön işlemciyi kullanma hakkında daha fazla bilgi sağlanır. Tercih ettiğiniz Visual Studio sürümünün belgelerini görmek için Sürüm seçici denetimini kullanın. Bu sayfadaki içindekiler tablosunun en üstünde bulunur.
Standart uyumluluğu geliştirmek, uzun süredir devam eden hataları düzeltmek ve resmi olarak tanımlanmamış bazı davranışları değiştirmek için Microsoft C++ önişlemcisini güncelleştiriyoruz. Makro tanımlarındaki hatalarla ilgili uyarı vermek için yeni tanılamalar da ekledik.
Visual Studio 2019 sürüm 16.5'den itibaren C++20 standardı için ön işlemci desteği özellik tamamlanır. Bu değişiklikler /Zc:önişlemci derleyici anahtarı kullanılarak kullanılabilir. Yeni ön işlemcinin deneysel bir sürümü, Visual Studio 2017 sürüm 15.8'den başlayarak önceki sürümlerde kullanılabilir. /experimental:preprocessor derleyici anahtarını kullanarak etkinleştirebilirsiniz. Varsayılan önişlemci davranışı önceki sürümlerle aynı kalır.
Önceden tanımlanmış yeni makro
Derleme zamanında hangi ön işlemcinin kullanıldığını algılayabilirsiniz. Geleneksel ön işlemcinin kullanımda olup olmadığını öğrenmek için önceden tanımlanmış makronun _MSVC_TRADITIONAL
değerini denetleyin. Bu makro, derleyicinin onu destekleyen sürümleri tarafından koşulsuz olarak ayarlanır ve önişlemci çağrılır. Geleneksel önişlemci için değeri 1'dir. Uyumlu önişlemci için 0'dır.
#if !defined(_MSVC_TRADITIONAL) || _MSVC_TRADITIONAL
// Logic using the traditional preprocessor
#else
// Logic using cross-platform compatible preprocessor
#endif
Yeni ön işlemcideki davranış değişiklikleri
Yeni önişlemci üzerindeki ilk çalışma, tüm makro genişletmelerinin standarda uygun hale getirilmesine odaklanmıştır. MSVC derleyicisini şu anda geleneksel davranışlar tarafından engellenen kitaplıklarla kullanmanıza olanak tanır. Güncelleştirilmiş ön işlemciyi gerçek dünya projelerinde test ettik. Bulduğumuz en yaygın hataya neden olan değişikliklerden bazıları şunlardır:
Makro açıklamaları
Geleneksel ön işlemci, ön işlemci belirteçleri yerine karakter arabelleklerini temel alır. Uyumlu önişlemci altında çalışmayan aşağıdaki ön işlemci açıklama numarası gibi olağan dışı davranışlara izin verir:
#if DISAPPEAR
#define DISAPPEARING_TYPE /##/
#else
#define DISAPPEARING_TYPE int
#endif
// myVal disappears when DISAPPEARING_TYPE is turned into a comment
DISAPPEARING_TYPE myVal;
Standartlara uygun düzeltme, uygun #ifdef/#endif
yönergelerin içinde bildirmektirint myVal
:
#define MYVAL 1
#ifdef MYVAL
int myVal;
#endif
L#val
Geleneksel önişlemci bir dize ön ekini yanlış bir şekilde dize işleci (#) işlecinin sonucuyla birleştirir:
#define DEBUG_INFO(val) L"debug prefix:" L#val
// ^
// this prefix
const wchar_t *info = DEBUG_INFO(hello world);
Bu durumda, L
bitişik dize değişmez değerleri makro genişletmeden sonra yine de birleştirildiğinden ön ek gereksizdir. Geriye dönük uyumlu düzeltme, tanımı değiştirmektir:
#define DEBUG_INFO(val) L"debug prefix:" #val
// ^
// no prefix
Aynı sorun, bağımsız değişkeni geniş bir dize değişmez değerine "dizeleştiren" kolaylık makrolarında da bulunur:
// The traditional preprocessor creates a single wide string literal token
#define STRING(str) L#str
Sorunu çeşitli yollarla düzeltebilirsiniz:
ön ek eklemek için ve
#str
dize birleştirmesiniL""
kullanın. Makro genişletmeden sonra bitişik dize değişmez değerleri birleştirilir:#define STRING1(str) L""#str
Ek makro genişletme ile dizelendirildikten sonra
#str
ön eki ekleme#define WIDE(str) L##str #define STRING2(str) WIDE(#str)
Belirteçleri birleştirmek için birleştirme işlecini
##
kullanın. ve#
için##
işlemlerin sırası belirtilmemiştir, ancak bu durumda tüm derleyiciler işleci daha önce##
değerlendirmiş#
gibi görünür.#define STRING3(str) L## #str
Geçersiz uyarı ##
Belirteç yapıştırma işleci (##) tek bir geçerli ön işleme belirteciyle sonuçlanmadığında davranış tanımlanmamış olur. Geleneksel ön işlemci, belirteçleri sessizce birleştiremiyor. Yeni önişlemci diğer derleyicilerin çoğuyla eşleşir ve bir tanılama yayar.
// 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;
Variadic makrolarda virgülle izdihaz
Geleneksel MSVC ön işlemcisi, boş __VA_ARGS__
değiştirmelerden önce her zaman virgülleri kaldırır. Yeni ön işlemci, diğer popüler platformlar arası derleyicilerin davranışını daha yakından izler. Virgül kaldırılabilmesi için variadic bağımsız değişkeninin eksik olması (yalnızca boş olmaması) ve işleçle ##
işaretlenmesi gerekir. Aşağıdaki örneği inceleyin:
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, );
}
Aşağıdaki örnekte, çağrılan makroda variadic bağımsız değişkenine yapılan çağrıda FUNC2(1)
eksiktir. Variadic bağımsız değişkenine FUNC2(1, )
yapılan çağrıda boş, ancak eksik değil (bağımsız değişken listesindeki virgüle dikkat edin).
#define FUNC2(a, ...) func(a , ## __VA_ARGS__)
int main()
{
// Expands to func(1)
FUNC2(1);
// Expands to func(1, )
FUNC2(1, );
}
Yaklaşan C++20 standardında, bu sorun eklenerek __VA_OPT__
giderilmiştir. için __VA_OPT__
yeni ön işlemci desteği Visual Studio 2019 sürüm 16.5'den itibaren kullanılabilir.
C++20 variadic makro uzantısı
Yeni önişlemci, C++20 variadic makro bağımsız değişken izdişini destekler:
#define FUNC(a, ...) __VA_ARGS__ + a
int main()
{
int ret = FUNC(0);
return ret;
}
Bu kod C++20 standardına uygun değildir. MSVC'de, yeni ön işlemci bu C++20 davranışını daha düşük dil standart modlarına (/std:c++14
, /std:c++17
) genişletir. Bu uzantı, diğer önemli platformlar arası C++ derleyicilerinin davranışıyla eşleşir.
Makro bağımsız değişkenleri "paketten çıkarılır"
Geleneksel ön işlemcide, bir makro bağımsız değişkenlerinden birini başka bir bağımlı makroya iletirse, eklendiğinde bağımsız değişken "paketten çıkarılmaz". Genellikle bu iyileştirme fark edilmeden gider, ancak olağan dışı davranışlara yol açabilir:
// 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", };
genişletirken A()
, geleneksel önişlemci içinde paketlenmiş __VA_ARGS__
tüm bağımsız değişkenleri TWO_STRINGS ilk bağımsız değişkenine iletir ve bu da boş olan değişken bağımsız değişkenini TWO_STRINGS
bırakır. Bu, sonucunun #first
yalnızca "1" yerine "1, 2" sonucuna neden olur. Yakından takip ediyorsanız, geleneksel ön işlemci genişletmesinin #__VA_ARGS__
sonucuna ne olduğunu merak ediyor olabilirsiniz: variadic parametresi boşsa boş dize değişmez değeriyle ""
sonuçlanmalıdır. Ayrı bir sorun, boş dize değişmez belirtecinin oluşturulmasını engeller.
Makrolar için değiştirme listesini yeniden tarama
Makro değiştirildikten sonra, ek makro tanımlayıcılarının değiştirilmesi için sonuçta elde edilen belirteçler yeniden taranılır. Bu örnekte gerçek koda göre gösterildiği gibi, geleneksel ön işlemci tarafından yeniden taramayı yapmak için kullanılan algoritma uyumlu değildir:
#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");
Bu örnek biraz karmaşık görünse de, bunu gerçek dünya kodunda gördük.
Neler olduğunu görmek için ile başlayan DO_THING
genişletmeyi ayırabiliriz:
DO_THING(1, "World")
genişletirCAT(IMPL, 1) ECHO(("Hello", "World"))
CAT(IMPL, 1)
olarak genişletirIMPL ## 1
ve genişletirIMPL1
- Artık belirteçler şu durumdadır:
IMPL1 ECHO(("Hello", "World"))
- Ön işlemci işlev benzeri makro tanımlayıcısını
IMPL1
bulur. bir tarafından takip(
edilmediğinden işlev benzeri bir makro çağrısı olarak kabul edilmez. - Ön işlemci aşağıdaki belirteçlere geçer. İşlev benzeri makronun
ECHO
çağrılmış olduğunu bulur:ECHO(("Hello", "World"))
olarak genişletilen("Hello", "World")
IMPL1
genişletme için bir daha asla dikkate alınmaz, bu nedenle genişletmelerin tam sonucu şu şekildedir:IMPL1("Hello", "World");
Makroyu hem yeni ön işlemcide hem de geleneksel ön işlemcide aynı şekilde davranacak şekilde değiştirmek için başka bir dolaylı katman ekleyin:
#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");
16.5 öncesi eksik özellikler
Visual Studio 2019 sürüm 16.5'den başlayarak, yeni ön işlemci C++20 için özellik tamamlama özelliğine sahiptir. Visual Studio'nun önceki sürümlerinde, bazı önişlemci yönerge mantığı hala geleneksel davranışa geri dönse de yeni önişlemci çoğunlukla tamamlanır. 16.5 öncesi Visual Studio sürümlerinde eksik özelliklerin kısmi listesi aşağıdadır:
- Için destek
_Pragma
- C++20 özellikleri
- Engelleme hatasını artırma: Ön işlemci sabit ifadelerindeki mantıksal işleçler, sürüm 16.5'den önceki yeni ön işlemcide tam olarak uygulanmaz. Bazı
#if
yönergelerde, yeni önişlemci geleneksel önişlemciye geri dönebilir. Bu etki yalnızca geleneksel önişlemciyle uyumsuz makrolar genişletildiğinde fark edilir. Boost ön işlemci yuvaları oluştururken bu durum oluşabilir.