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ştirmesini L"" 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_THINGgenişletmeyi ayırabiliriz:

  1. DO_THING(1, "World") genişletir CAT(IMPL, 1) ECHO(("Hello", "World"))
  2. CAT(IMPL, 1) olarak genişletir IMPL ## 1ve genişletir IMPL1
  3. Artık belirteçler şu durumdadır: IMPL1 ECHO(("Hello", "World"))
  4. Ön işlemci işlev benzeri makro tanımlayıcısını IMPL1bulur. bir tarafından takip (edilmediğinden işlev benzeri bir makro çağrısı olarak kabul edilmez.
  5. Ö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")
  6. 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.