Улучшения соответствия C++, изменения в поведении и исправления ошибок в Visual Studio 2022

В каждом выпуске Microsoft C/C++ в Visual Studio (MSVC) добавляются улучшения соответствия и исправления ошибок. В этой статье перечислены значительные улучшения по основным выпускам, а затем по версии. Чтобы перейти непосредственно к изменениям для конкретной версии, используйте ссылки на статью "В этой статье ".

В этом документе перечислены изменения в Visual Studio 2022.

Сведения об изменениях в Visual Studio 2019 см. в улучшениях соответствия C++ в Visual Studio 2019.
Изменения в Visual Studio 2017 см. в статье Улучшения соответствия C++ в Visual Studio 2017.
Сведения об изменениях в более ранних версиях см. в статье Visual C++ What's New 2003–2015.

Улучшения соответствия в Visual Studio 2022 версии 17.9

Visual Studio 2022 версии 17.9 содержит следующие улучшения соответствия, исправления ошибок и изменения поведения в компиляторе Microsoft C/C++.

Более широкая сводка изменений, внесенных в стандартную библиотеку шаблонов, см. в статье STL Changelog VS 2022 17.9.

Применение структурированного _Alignas типа в C

В версиях Visual C++ до Visual Studio 2022 версии 17.9, когда _Alignas появился рядом с типом структуры в объявлении, он не был применен правильно в соответствии со стандартом ISO-C. Например:

// compile with /std:c17
#include <stddef.h>
struct Outer
{
    _Alignas(32) struct Inner { int i; } member1;
    struct Inner member2;
};
static_assert(offsetof(struct Outer, member2)==4, "incorrect alignment");

Согласно стандарту ISO-C, этот код должен компилироваться без static_assert создания диагностики. Директива _Alignas применяется только к переменной-члену member1. Он не должен изменять выравнивание struct Inner. Однако до выпуска 17.9.1 Visual Studio была создана диагностика "неправильное выравнивание". Компилятор выровнен member2 с смещением 32 байтов в пределах struct Outer.

Исправление этого является двоичным критическим изменением, поэтому при применении этого изменения возникает предупреждение. В приведенном выше коде предупреждение C5274 больше_Alignas не применяется к типу Inner (применяется только к объявленным объектам данных)" теперь создается на уровне предупреждения 1.

В предыдущих версиях Visual Studio _Alignas не учитывается, когда он появился рядом с объявлением анонимного типа. Например:

// compile with /std:c17
#include <stddef.h>
struct S {
    _Alignas(32) struct { int anon_member; };
    int k;
};
static_assert(offsetof(struct S, k)==4, "incorrect offsetof");
static_assert(sizeof(struct S)==32, "incorrect size");

Ранее оба static_assert оператора завершились ошибкой при компиляции этого кода. Теперь код компилируется, но с помощью следующих предупреждений уровня 1:

warning C5274: behavior change: _Alignas no longer applies to the type '<unnamed-tag>' (only applies to declared data objects)
warning C5273: behavior change: _Alignas on anonymous type no longer ignored (promoted members will align)

Если требуется более раннее поведение, замените _Alignas(N) на __declspec(align(N)). В отличие от _Alignasтипа, declspec(align) можно применить к типу.

__VA_OPT__ включен в качестве расширения в разделе /Zc:preprocessor

__VA_OPT__ добавлен в C++20 и C23. Раньше к его добавлению не было стандартного способа точить запятую в вариадичном макросе. Для обеспечения более эффективной обратной совместимости __VA_OPT__ включен препроцессор /Zc:preprocessor на основе маркеров во всех языковых версиях.

Например, теперь эта компиляция выполняется без ошибок:

#define LOG_WRAPPER(message, ...) WRITE_LOG(__LINE__, message __VA_OPT__(, __VA_ARGS__))

// Failed to build under /std:c11, now succeeds.
LOG_WRAPPER("Log message");
LOG_WRAPPER("Log message with %s", "argument")

Язык C23

Для C23 при использовании переключателя компилятора /std:clatest доступны следующие возможности:

typeof
typeof_unqual

Для всех языковых версий C доступны следующие компоненты:

__typeof__
__typeof_unqual__

стандартная библиотека C++

Функции C++23

Улучшения соответствия в Visual Studio 2022 версии 17.8

Visual Studio 2022 версии 17.8 содержит следующие улучшения соответствия, исправления ошибок и изменения поведения в компиляторе Microsoft C/C++.

/FU проблемы с ошибкой

Компилятор C, используемый для принятия /FU параметра, даже если он не поддерживает управляемую компиляцию в течение некоторого времени. Теперь она выдает ошибку. Проекты, которые передают этот параметр, должны ограничить его только проектами C++/CLI.

стандартная библиотека C++

Именованные модули std C++23 и std.compat теперь доступны при компиляции с /std:c++20помощью .

Более широкая сводка изменений, внесенных в стандартную библиотеку C++, см. в статье STL Changelog VS 2022 17.8.

Улучшения соответствия в Visual Studio 2022 версии 17.7

Visual Studio 2022 версии 17.7 содержит следующие выделенные улучшения соответствия, исправления ошибок и изменения поведения в компиляторе Microsoft C/C++.

Добавлен /std:clatest в компилятор C

Этот параметр ведет себя как /std:c++latest переключатель для компилятора C++. Этот коммутатор включает все реализованные в настоящее время функции компилятора и стандартной библиотеки, предлагаемые для следующего проекта стандарта C, а также некоторые текущие и экспериментальные функции.

стандартная библиотека C++

Теперь <print> библиотека поддерживается. См . P2093R14 форматированные выходные данные.

Реализовано views::cartesian_product.

Более широкая сводка изменений, внесенных в стандартную библиотеку шаблонов, см. в статье STL Changelog VS 2022 17.7.

using Соответствия

Ранее директива using может привести к тому, что имена из используемых пространств имен остаются видимыми, когда они не должны. Это может привести к поиску неквалифицированного имени в пространстве имен, даже если директива не using активна.

Ниже приведены некоторые примеры нового и старого поведения.
Ссылки на следующие примечания к "(1)" означают вызов f<K>(t) в пространстве Aимен:

namespace A
{ 
    template<typename K, typename T> 
    auto f2(T t)
    { 
        return f<K>(t); // (1) Unqualified lookup should not find anything
    } 
} 

namespace B
{ 
    template<typename K, typename T> 
    auto f(T t) noexcept
    { // Previous behavior: This function was erroneously found during unqualified lookup at (1)
        return A::f2<K>(t); 
    } 
} 

namespace C
{ 
    template<typename T> 
    struct S {}; 

    template<typename, typename U> 
    U&& f(U&&) noexcept; // New behavior: ADL at (1) correctly finds this function 
} 

namespace D
{ 
    using namespace B; 

    void h()
    { 
        D::f<void>(C::S<int>()); 
    } 
} 

Та же основная проблема может привести к отклонению кода, который ранее скомпилирован:

#include <memory>
namespace Addin {}
namespace Gui
{
    using namespace Addin;
}

namespace Addin
{
    using namespace std;
}

// This previously compiled, but now emits error C2065 for undeclared name 'allocator'.
// This should be declared as 'std::allocator<T*>' because the using directive nominating
// 'std' is not active at this point.
template <class T, class U = allocator<T*>>
class resource_list
{
};

namespace Gui
{
    typedef resource_list<int> intlist;
}

Улучшения соответствия в Visual Studio 2022 версии 17.6

Visual Studio 2022 версии 17.6 содержит следующие улучшения соответствия, исправления ошибок и изменения поведения в компиляторе Microsoft C/C++.

Составные volatile назначения больше не рекомендуется

C++20 не рекомендуется применять определенные операторы к типам, volatileсоответствующим требованиям. Например, если следующий код компилируется с cl /std:c++20 /Wall test.cppпомощью:

void f(volatile int& expr)
{
   ++expr;
}

Компилятор создает test.cpp(3): warning C5214: applying '++' to an operand with a volatile qualified type is deprecated in C++20.

В C++20 не рекомендуется использовать операторы составного назначения (операторы формы @=). В C++23 составные операторы, исключенные в C++20, больше не рекомендуется. Например, в C++23 следующий код не создает предупреждение, в то время как он выполняется в C++20:

void f(volatile int& e1, int e2)
{
   e1 += e2;
}

Дополнительные сведения об этом изменении см. в разделе CWG:2654

Перезапись равенства в выражениях меньше критического изменения (P2468R2)

В C++20 P2468R2 изменили компилятор, чтобы принять код, например:

struct S
{
    bool operator==(const S&);
    bool operator!=(const S&);
};
bool b = S{} != S{};

Компилятор принимает этот код, что означает, что компилятор более строгий с кодом, таким как:

struct S
{
  operator bool() const;
  bool operator==(const S&);
};

bool b = S{} == S{};

Версия 17.5 компилятора принимает эту программу. Версия 17.6 компилятора отклоняет его. Чтобы исправить его, добавьте const , чтобы operator== удалить неоднозначность. Или добавьте соответствующее operator!= определение, как показано в следующем примере:

struct S
{
  operator bool() const;
  bool operator==(const S&);
  bool operator!=(const S&);
};

bool b = S{} == S{};

Компилятор Microsoft C/C++ версии 17.5 и 17.6 принимают предыдущую программу и вызовы S::operator== в обеих версиях.

Общая модель программирования, описанная в P2468R2, заключается в том, что при наличии соответствующего operator!= типа он обычно подавляет поведение перезаписи. Добавление соответствующего operator!= является предлагаемым исправлением для кода, ранее скомпилированного в C++17. Дополнительные сведения см. в разделе "Модель программирования".

Улучшения соответствия в Visual Studio 2022 версии 17.4

Visual Studio 2022 версии 17.4 содержит следующие улучшения соответствия, исправления ошибок и изменения поведения в компиляторе Microsoft C/C++.

Базовые типы un область d enum без фиксированного типа

В версиях Visual Studio до Visual Studio 2022 версии 17.4 компилятор C++ неправильно определил базовый тип перечисления un область d без фиксированного базового типа. В разделе /Zc:enumTypesтеперь мы правильно реализуем стандартное поведение.

Стандарт C++ требует наличия достаточно большого enum базового типа для хранения всех перечислителей.enum Достаточно крупные перечислители могут задать базовый enumunsigned intтип для , long longили unsigned long long. Ранее такие enum типы всегда имели базовый тип int в компиляторе Майкрософт независимо от значений перечислителя.

При включении этот /Zc:enumTypes параметр является потенциальным источником и двоичным критическим изменением. Он отключен по умолчанию и не включен /permissive-, так как исправление может повлиять на совместимость двоичных файлов. Некоторые типы перечисления изменяют размер при включении соответствующего исправления. Некоторые заголовки пакета SDK для Windows включают такие определения перечисления.

Пример

enum Unsigned
{
    A = 0xFFFFFFFF // Value 'A' does not fit in 'int'.
};

// Previously, failed this static_assert. Now passes with /Zc:enumTypes.
static_assert(std::is_same_v<std::underlying_type_t<Unsigned>, unsigned int>);

template <typename T>
void f(T x)
{
}

int main()
{
    // Previously called f<int>, now calls f<unsigned int>.
    f(+A);
}

// Previously this enum would have an underlying type of `int`, but Standard C++ requires this to have
// a 64-bit underlying type. Using /Zc:enumTypes changes the size of this enum from 4 to 8, which could
// impact binary compatibility with code compiled with an earlier compiler version or without the switch.
enum Changed
{
    X = -1,
    Y = 0xFFFFFFFF
};

Типы перечислителей в определении enum без фиксированного базового типа

В версиях Visual Studio до Visual Studio 2022 версии 17.4 компилятор C++ не правильно моделировал типы перечислителей. Он может предположить неправильный тип перечисления без фиксированного базового типа перед закрывающей скобкой перечисления. В разделе /Zc:enumTypesкомпилятор теперь правильно реализует стандартное поведение.

Стандарт C++ указывает, что в определении перечисления не фиксированного базового типа инициализаторы определяют типы перечислителей. Или для перечислителей без инициализатора по типу предыдущего перечислителя (учет переполнения). Ранее такие перечислители всегда были даны выводимый тип перечисления с заполнителем базового типа (обычно int).

При включении этот /Zc:enumTypes параметр является потенциальным источником и двоичным критическим изменением. Он отключен по умолчанию и не включен /permissive-, так как исправление может повлиять на совместимость двоичных файлов. Некоторые типы перечисления изменяют размер при включении соответствующего исправления. Некоторые заголовки пакета SDK для Windows включают такие определения перечисления.

Пример

enum Enum {
    A = 'A',
    B = sizeof(A)
};

static_assert(B == 1); // previously failed, now succeeds under /Zc:enumTypes

В этом примере перечислитель A должен иметь тип char перед закрывающей скобкой перечисления, поэтому B его следует инициализировать с помощью sizeof(char). Перед исправлением /Zc:enumTypesA был тип Enum перечисления с выведенным базовым типом intи инициализирован B с помощью sizeof(Enum)или 4.

Улучшения соответствия в Visual Studio 2022 версии 17.3

Visual Studio 2022 версии 17.3 содержит следующие улучшения соответствия, исправления ошибок и изменения поведения в компиляторе Microsoft C/C++.

C: улучшена проверка совместимости модификаторов указателей

Компилятор C неправильно сравнивал модификаторы указателей, в особенности void*. Этот дефект может привести к неправильной диагностике несовместимости между const int** и void* совместимостью между int* volatile* и void*.

Пример

void fn(void* pv) { (pv); }

int main()
{
    int t = 42;
    int* pt = &t;
    int* volatile * i = &pt;
    fn(i);    // Now raises C4090
    const int** j = &pt;
    fn(j);    // No longer raises C4090
}

Улучшения соответствия в Visual Studio 2022 версии 17.2

Visual Studio 2022 версии 17.2 содержит следующие улучшения соответствия, исправления ошибок и изменения поведения в компиляторе Microsoft C/C++.

Предупреждения о двунаправленных символах без ограничений

В Visual Studio 2022 версии 17.2 добавлено предупреждение C5255 уровня 3 для незавершенных двунаправленных символов Юникода в комментариях и строках. Предупреждение касается проблемы безопасности, описанной в статье, посвященной программам-троянам (невидимые уязвимости) Николаса Баучера (Nicholas Boucher) и Росса Андерсона (Ross Anderson). Дополнительные сведения о двунаправленных символах Unicode см. в приложении № 9 к стандарту Unicode® с описанием двунаправленного алгоритма Юникода.

Предупреждение C5255 касается только файлов, которые после преобразования содержат двунаправленные символы Unicode. Это предупреждение относится к файлам UTF-8, UTF-16 и UTF-32, поэтому необходимо указать правильную исходную кодировку. Это критическое изменение исходного кода.

Пример (до и после)

В версиях Visual Studio до Visual Studio 2022 версии 17.2 незавершенный двунаправленный символ не вызывал предупреждения. В Visual Studio 2022 версии 17.2 возникает предупреждение C5255:

// bidi.cpp
int main() {
    const char *access_level = "user";
    // The following source line contains bidirectional Unicode characters equivalent to:
    //    if ( strcmp(access_level, "user\u202e \u2066// Check if admin \u2069 \u2066") ) {
    // In most editors, it's rendered as:
    //    if ( strcmp(access_level, "user") ) { // Check if admin
    if ( strcmp(access_level, "user‮ ⁦// Check if admin ⁩ ⁦") ) {
        printf("You are an admin.\n");
    }
    return 0;
}

/* build output
bidi.cpp(8): warning C5255: unterminated bidirectional character encountered: 'U+202e'
bidi.cpp(8): warning C5255: unterminated bidirectional character encountered: 'U+2066'
*/

from_chars()Схема разрешения конфликтов float

Visual Studio 2022 версии 17.2 исправляет ошибку в правилах схемы разрешения конфликтов <charconv>from_chars()float, которая приводила к неправильным результатам. Эта ошибка затрагивала десятичные строки, которые находились точно посередине последовательных значений float в узком диапазоне. (Наименьшие и самые крупные затронутые значения были 32768.009765625 и 131071.98828125соответственно.) Правило тай-брейка хотело округить до "даже", и "даже" произошло "вниз", но реализация неправильно округлила "вверх" (double не пострадала). Дополнительные сведения и сведения о реализации см. в статье microsoft/STL#2366.

Это изменение влияет на поведение среды выполнения в указанном диапазоне вариантов:

Пример

// from_chars_float.cpp
#include <cassert>
#include <charconv>
#include <cstdio>
#include <string_view>
#include <system_error>
using namespace std;
int main() {
    const double dbl  = 32768.009765625;
    const auto sv     = "32768.009765625"sv;
    float flt         = 0.0f;
    const auto result = from_chars(sv.data(), sv.data() + sv.size(), flt);
    assert(result.ec == errc{});
    printf("from_chars() returned: %.1000g\n", flt);
    printf("This rounded %s.\n", flt < dbl ? "DOWN" : "UP");
}

В версиях до Visual Studio 2022 версии 17.2:

C:\Temp>cl /EHsc /nologo /W4 /std:c++17 from_chars_float.cpp && from_chars_float
from_chars_float.cpp
from_chars() returned: 32768.01171875
This rounded UP.

В Visual Studio 2022 версии 17.2 и более поздних:

C:\Temp>cl /EHsc /nologo /W4 /std:c++17 from_chars_float.cpp && from_chars_float
from_chars_float.cpp
from_chars() returned: 32768.0078125
This rounded DOWN.

/Zc:__STDC__ предоставляет __STDC__ для C

Стандарт C требует, чтобы соответствующая реализация C определяла __STDC__ как 1. Из-за поведения UCRT без предоставления функций POSIX, когда __STDC__ равно 1, невозможно определить этот макрос для C по умолчанию без внесения критических изменений в стабильные языковые версии. Visual Studio 2022 версии 17.2 и более поздних версий добавьте параметр /Zc:__STDC__ соответствия, определяющий этот макрос. Нет отрицательной версии параметра. В настоящее время мы планируем использовать этот параметр по умолчанию для будущих версий C.

Это критическое изменение исходного кода. Это применимо, когда включен режим C11 или C17, /std:c11 или /std:c17 вместе с /Zc:__STDC__.

Пример

// test__STDC__.c
#include <io.h>
#include <fcntl.h>
#include <stdio.h>

int main() {
#if __STDC__
    int f = _open("file.txt", _O_RDONLY);
    _close(f);
#else
    int f = open("file.txt", O_RDONLY);
    close(f);
#endif
}

/* Command line behavior

C:\Temp>cl /EHsc /W4 /Zc:__STDC__ test__STDC__.c && test__STDC__

*/

Предупреждение об отсутствии фигурных скобок

Предупреждение C5246 сообщает об отсутствии фигурных скобок во время агрегатной инициализации подобъекта. До Visual Studio 2022 версии 17.2 предупреждение не обрабатывало случай с анонимными struct или union.

Это критическое изменение исходного кода. Это применимо, когда включено предупреждение C5246 по умолчанию.

Пример

В Visual Studio 2022 версии 17.2 и выше этот код теперь вызывает ошибку:

struct S {
   union {
      float f[4];
      double d[2];
   };
};

void f()
{
   S s = { 1.0f, 2.0f, 3.14f, 4.0f };
}

/* Command line behavior
cl /Wall /c t.cpp

t.cpp(10): warning C5246: 'anonymous struct or union': the initialization of a subobject should be wrapped in braces
*/

Чтобы решить эту проблему, добавьте фигурные скобки к инициализатору:

void f()
{
   S s = { { 1.0f, 2.0f, 3.14f, 4.0f } };
}

Улучшения соответствия в Visual Studio 2022 версии 17.1

Visual Studio 2022 версии 17.1 содержит следующие улучшения соответствия, исправления ошибок и изменения поведения в компиляторе Microsoft C/C++.

Обнаружение неправильно сформированного захвата по умолчанию в нелокальных лямбда-выражениях

Стандартная библиотека C++ позволяет только лямбда-выражению в области блока выполнять запись по умолчанию. В Visual Studio 2022 версии 17.1 и более поздних версиях компилятор обнаруживает, когда по умолчанию запись не разрешена в нелокальном лямбда-выражении. Выдается новое предупреждение C5253 уровня 4.

Это критическое изменение исходного кода. Оно применяется в любом режиме, в котором используется новый лямбда-процессор: /Zc:lambda, /std:c++20 или /std:c++latest.

Пример

В Visual Studio 2022 версии 17.1 этот код теперь выдает ошибку:

#pragma warning(error:5253)

auto incr = [=](int value) { return value + 1; };

// capture_default.cpp(3,14): error C5253: a nonlocal lambda cannot have a capture default
// auto incr = [=](int value) { return value + 1; };
//              ^

Чтобы устранить эту проблему, удалите запись по умолчанию:

#pragma warning(error:5253)

auto incr = [](int value) { return value + 1; };

C4028 теперь представляет C4133 для операций преобразования функции в указатель.

До Visual Studio 2022 версии 17.1 компилятор сообщал о неправильном сообщении об ошибке при некоторых сравнениях указателя с функцией в коде C. Сообщалось о неправильном сообщении при сравнении двух указателей на функции с одинаковым числом аргументов, но несовместимыми типами. Теперь мы выдаем другое предупреждение о несовместимости указателя и функции, а не о несоответствии параметров функции.

Это критическое изменение исходного кода. Оно применяется, когда код компилируется как код C.

Пример

int f1(int); 
int f2(char*); 
int main(void) 
{ 
    return (f1 == f2); 
}
// Old warning:
// C4028: formal parameter 1 different from declaration
// New warning:
// C4113: 'int (__cdecl *)(char *)' differs in parameter lists from 'int (__cdecl *)(int)'

Ошибка в неотключаемом static_assert

В Visual Studio 2022 версии 17.1 и более поздних версиях, если выражение, связанное с static_assert выражением, не является зависимым выражением, компилятор оценивает выражение при анализе. Если выражение вычисляется как false, компилятор выдает ошибку. Раньше при размещении static_assert в теле шаблона функции (или в теле функции-члена шаблона класса) компилятор не выполнял этот анализ.

Это критическое изменение исходного кода. Оно применяется в любом режиме, который подразумевает /permissive- или /Zc:static_assert. Это изменение в поведении можно отключить с помощью параметра компилятора /Zc:static_assert-.

Пример

В Visual Studio 2022 версии 17.1 и выше этот код теперь вызывает ошибку:

template<typename T>
void f()
{
   static_assert(false, "BOOM!");
}

Чтобы устранить эту проблему, сделайте выражение зависимым. Например:

template<typename>
constexpr bool dependent_false = false;

template<typename T>
void f()
{
   static_assert(dependent_false<T>, "BOOM!");
}

С этим изменением компилятор выдает ошибку, только если создается экземпляр f шаблона функции.

Улучшения соответствия в Visual Studio 2022 версии 17.0

Visual Studio 2022 версии 17.0 содержит следующие улучшения соответствия, исправления ошибок и изменения поведения в компиляторе Microsoft C/C++.

Предупреждение о ширине битового поля для типа перечисления

Если экземпляр типа перечисления объявляется как битовое поле, его ширина должна вмещать все возможные значения перечисления. В противном случае компилятор выдаст диагностическое сообщение. Рассмотрим следующий пример:

enum class E : unsigned { Zero, One, Two };

struct S {
  E e : 1;
};

Программист ожидает, что член класса S::e может содержать любые из явно именованных enum значений. С учетом количества элементов перечисления это невозможно. Битовое поле не может охватывать диапазон явно указанных значений E (по сути, доменE). Чтобы устранить проблему недостаточного размера битового поля для домена перечисления, в MSVC добавляется новое предупреждение (отключено по умолчанию):

t.cpp(4,5): warning C5249: 'S::e' of type 'E' has named enumerators with values that cannot be represented in the given bit field width of '1'.
  E e : 1;
    ^
t.cpp(1,38): note: see enumerator 'E::Two' with value '2'
enum class E : unsigned { Zero, One, Two };
                                     ^

Это поведение компилятора является критическим изменением исходного и двоичного кодов и затрагивает все /std и /permissive режимы.

Ошибка при упорядоченном сравнении указателей с nullptr или 0

В стандарте C++ было непреднамеренно разрешено упорядоченное сравнение указателей с nullptr или 0. Например:

bool f(int *p)
{
   return p >= 0;
}

В документе WG21 №3478 эта ошибка была исправлена. Это изменение реализуется в MSVC. Когда пример компилируется с помощью /permissive-/diagnostics:caret), возвращается следующая ошибка:

t.cpp(3,14): error C7664: '>=': ordered comparison of pointer and integer zero ('int *' and 'int')
    return p >= 0;
             ^

Это поведение компилятора является критическим изменением исходного и двоичного кодов и влияет на код, скомпилированный с помощью /permissive- во всех /std режимах.

См. также

Соответствие стандартам языка Microsoft C/C++