Visual Studio 2022 中的 C++ 一致性改进、行为更改和 bug 修复

Visual Studio 中的 Microsoft C++ (MSVC) 在每个版本中进行了符合性改进和 bug 修复。 本文按主要版本、然后是次要版本的形式列出了显著改进之处。 若要直接跳转到特定版本的更改,请使用 本文 顶部的“在本文中”链接。

本文档列出了 Visual Studio 2022 中的更改。

对于早期版本的 Visual Studio 中的更改:

版本 符合性改进链接
2019 Visual Studio 2019 中的 C++ 一致性改进
2017 Visual Studio 2017 中 C++ 的符合性改进
2003-2015 Visual C++ 新增功能 (2003 - 2015)

Visual Studio 2022 版本 17.12 中的符合性改进

Visual Studio 2022 版本 17.12 包括以下符合性改进、bug 修复和Microsoft C/C++ 编译器中的行为更改。

有关对标准模板库所做的更改的深入摘要,包括符合性更改、bug 修复和性能改进,请参阅 STL Changelog VS 2022 17.12

_com_ptr_t::operator bool() 现在为显式

这是源/二进制中断性变更。

从实例进行隐式转换bool_com_ptr_t可能会令人惊讶或导致编译器错误。 C++核心准则(C.164)禁止隐式转换函数,并_com_ptr_t包含对两者的boolInterface*隐式转换。 这两个隐式转换可能会导致歧义。

为了帮助解决此问题,转换到 bool 现在是显式的。 转换到 Interface* 的更改不变。

提供了一个宏来选择退出此新行为并还原以前的隐式转换。 编译 /D_COM_DISABLE_EXPLICIT_OPERATOR_BOOL 为选择退出此更改。 建议修改代码以不依赖于隐式转换。

例如:

#include <comip.h>

template<class Iface>
using _com_ptr = _com_ptr_t<_com_IIID<Iface, &__uuidof(Iface)>>;

int main()
{
   _com_ptr<IUnknown> unk;
   if (unk) // Still valid
   { 
      // ...
   }
   bool b = unk; // Still valid.
   int v = unk; // Previously permitted, now emits C2240: cannot convert from '_com_ptr_t<_com_IIID<IUnknown,& _GUID_00000000_0000_0000_c000_000000000046>>' to 'int'
}

常量表达式不再始终 noexcept 处于宽松模式

这是源/二进制中断性变更。

始终是 noexcept常量表达式,即使它涉及对使用可能引发异常规范声明的函数的函数调用也是如此。 尽管 Microsoft Visual C++ 编译器在所有C++语言版本中仍以模式支持此措辞,但在 C++17 中删除了此 /permissive 措辞。

删除此 /permissive 模式行为。 常量表达式不再具有特殊的隐式行为。

函数 noexcept 说明 constexpr 符现在在所有模式下都受到尊重。 对于依赖标准 noexcept 行为的后续核心问题解决方案的正确实现,需要进行此更改。

例如:

constexpr int f(bool b) noexcept(false)
{ 
    if (b)
    {
        throw 1;
    }
    else
    {
        return 1;
    }
}

void g(bool b)
{
   noexcept(f(b)); // false. No change to behavior
   noexcept(f(true)); // false. No change to behavior
   noexcept(f(false)); // false. Was true in /permissive mode only in previous versions.
}

Visual Studio 2022 版本 17.11 中的符合性改进

Visual Studio 2022 版本 17.11 包括以下符合性改进、bug 修复和Microsoft C/C++ 编译器中的行为更改。

有关对标准模板库所做的更改的深入摘要,包括符合性更改、bug 修复和性能改进,请参阅 STL Changelog VS 2022 17.11

根据 P3142R0,现在使用 生成空白行 println很容易。 使用 . 进行 /std:c++latest编译时,此功能可用。 在这次更改之前,你写道:println("");现在你写: println();

  • println(); 等效于 println(stdout);
  • println(FILE* stream); 等效于 println(stream, "\n");

实现 range_formatter

现在已range_formatter实现每个P2286R8。 使用 . 进行 /std:c++latest编译时,此功能可用。

Visual Studio 2022 版本 17.10 中的符合性改进

Visual Studio 2022 版本 17.10 包括以下符合性改进、bug 修复和Microsoft C/C++ 编译器中的行为更改。

有关对标准模板库所做的更改的深入摘要,包括符合性更改、bug 修复和性能改进,请参阅 STL Changelog VS 2022 17.10

具有显式指定的返回类型的转换运算符专用化

在某些情况下,编译器用于错误地专用转换运算符,这可能会导致返回类型不匹配。 这些无效的专用化不再发生。 这是源代码中断性变更。

// Example 1
struct S
{
    template<typename T> operator const T*();
};

void test()
{
    S{}.operator int*(); // this is invalid now
    S{}.operator const int*(); // this is valid
}
// Example 2
// In some cases, the overload resolution result may change
struct S
{
    template <typename T> operator T*(); // overload 1
    template <typename T> operator const T*(); // overload 2
};

void test()
{
    S{}.operator int*(); // this used to call overload 2, now it calls overload 1
}

添加了对 #elifdef#elifndef 的支持

添加了对 WG21 P2334R1 (C++23) 和 WG14 N2645 (C++23) 的支持,引入了 #elifdef#elifndef 预处理器指令。 需要 /std:clatest/std:c++latest

之前:

#ifdef __cplusplus
  #include <atomic>
#elif !defined(__STDC_NO_ATOMICS__)
  #include <stdatomic.h>
#else
  #include <custom_atomics_library.h>
#endif

之后:

#ifdef __cplusplus
  #include <atomic>
#elifndef __STDC_NO_ATOMICS__
  #include <stdatomic.h>
#else
  #include <custom_atomics_library.h>
#endif

在 C 中对结构化类型应用 _Alignas

适用于 C 语言(C17 及更高版本)。 还添加到 Microsoft Visual Studio 17.9

在 Visual Studio 2022 版本 17.9 之前的 Visual C++ 版本中,如果 _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 的对齐方式。 但是,在 Visual Studio 17.9.1 之前,发出了诊断“不正确的对齐方式”。 编译器 member2struct Outer 类型内 32 个字节的偏移量对齐。

这是二进制中断性变更,因此,此更改生效时,现在会发出警告。 对于前面的示例,警告 C5274 现在以警告级别 1 发出: warning C5274: behavior change: _Alignas no longer applies to the type 'Inner' (only applies to declared data objects)

此外,在早期版本的 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) 适用于该类型。

改进了警告 C4706

这是源代码中断性变更。 以前,编译器未检测到将赋值包装在括号中的约定(如果指定赋值),以便禁止警告 C4706 条件表达式中的赋值。 编译器现在会检测括号并禁止显示警告。

#pragma warning(error: 4706)

struct S
{
   auto mf()
   {
      if (value = 9)
         return value + 4;
      else
         return value;
   }

   int value = 9;
};

编译器现在还会在未引用函数的情况下发出警告。 以前,由于 mf 是未引用的内联函数,因此不会为此代码发出警告 C4706。 现在会发出警告:

error C4706: assignment used as a condition
note: if an assignment is intended you can enclose it in parentheses, '(e1 = e2)', to silence this warning

若要修复此警告,请使用相等运算符 value == 9(如果这是预期)。 或者,用括号括起赋值 (value = 9)(如果有赋值)。 否则,由于函数未推理,请将其移除。

Visual Studio 2022 版本 17.9 中的符合性改进

Visual Studio 2022 版本 17.9 包含 Microsoft C/C++ 编译器中的以下符合性改进、bug 修复和行为变更。

有关对标准模板库所做的更改的更广泛摘要,请参阅 STL Changelog VS 2022 17.9

在 C 中对结构化类型应用 _Alignas

在 Visual Studio 2022 版本 17.9 之前的 Visual C++ 版本中,当 _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 的对齐方式。 但是,在 Visual Studio 版本 17.9.1 之前,发出了诊断“不正确的对齐方式”。 编译器将 member2struct Outer 中的 32 字节偏移量对齐。

修复此问题是二进制中断性变更,因此当应用此行为更改时,将发出警告。 对于前面的代码,警告 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 下的扩展启用

C++20 和 C23 中添加了 __VA_OPT__。 在添加之前,没有一种标准方法可以在可变参数宏中删除逗号。 为了提供更好的后向兼容性,我们在所有语言版本的基于令牌的预处理器 /Zc:preprocessor 下启用了 __VA_OPT__

例如,现在编译没有错误:

#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 功能

  • formattablerange_formatformat_kindset_debug_format()P2286R8 格式设置范围的一部分
  • <mdspan>,遵循 P0009R18 以及应用于 C++23 Standard 的后续措辞更改。
  • format() 指针,遵循 P2510R3

Visual Studio 2022 版本 17.8 中的符合性改进

Visual Studio 2022 版本 17.8 包含 Microsoft C/C++ 编译器中的以下符合性改进、bug 修复和行为变更。

/FU 发出错误

C 编译器用于接受 /FU 选项,尽管该编译器已经有一段时间不支持托管编译了。 它现在会发出错误。 传递此选项的项目需要将其仅限于 C++/CLI 项目。

C++ 标准库

C++23 命名模块 stdstd.compat 现在可在编译 /std:c++20 时使用。

有关对 C++ 标准库所做的更改的更广泛摘要,请参阅 STL Changelog VS 2022 17.8

Visual Studio 2022 版本 17.7 中的符合性改进

Visual Studio 2022 版本 17.7 包含 Microsoft C/C++ 编译器中的以下符合性改进、bug 修复和行为变更。

向 C 编译器添加了 /std:clatest

此开关的行为类似于 C++ 编译器的 /std:c++latest 开关。 此开关启用了为下一个 C 标准草案提出的所有当前实现的编译器和标准库功能,以及一些正在进行和实验的功能。

C++ 标准库

现在支持 <print> 库。 请参阅 P2093R14 格式化输出

实现了 views::cartesian_product

有关对标准模板库所做的更改的更广泛摘要,请参阅 STL Changelog VS 2022 17.7

using 符合性

以前,using 指令可能会导致已用命名空间中的名称在不应显示时保持可见。 这可能会导致非限定名称查找在命名空间中查找名称,即使没有处于活动状态的 using 指令。

下面是新旧行为的一些示例。
以下注释中对“(1)”的引用意味着在命名空间 A 中调用 f<K>(t)

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++ 编译器的以下合规性改进、bug 修复和行为变更。

不再弃用复合 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!=,它通常会抑制重写行为。 对于以前在 C++17 中编译的代码,添加相应的 operator!= 是建议的修补方式。 有关详细信息,请参阅编程模型

Visual Studio 2022 版本 17.4 中的符合性改进

Visual Studio 2022 版本 17.4 包含 Microsoft C/C++ 编译器的以下符合性改进、bug 修复和行为变更。

无固定类型且未区分范围的 enum 的基础类型

在 Visual Studio 2022 版本 17.4 之前的 Visual Studio 版本中,C++ 编译器未正确确定无固定基类型的未区分范围的枚举的基础类型。 在 /Zc:enumTypes 下,我们现在正确地实现了标准行为。

C++ 标准要求 enum 的基础类型必须足够大,才能容纳该 enum 中的所有枚举器。 足够大的枚举器可以将 enum 的基础类型设置为 unsigned intlong longunsigned long long。 以前,这种 enum 类型在 Microsoft 编译器中始终具有基础类型 int,无需考虑枚举器的值。

启用 /Zc:enumTypes 选项后,它是一个潜在的源和二进制中断性变更。 默认情况下,它处于关闭状态,并且不会由 /permissive- 启用,因为该修复可能会影响二进制兼容性。 启用符合性修复后,一些枚举类型会改变大小。 一些 Windows SDK 标头包括此类枚举定义。

示例

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 2022 版本 17.4 之前的 Visual Studio 版本中,C++ 编译器未正确地为枚举器类型建模。 如果在枚举的右大括号前面没有固定的基础类型,它可能会在枚举中假定一个不正确的类型。 在 /Zc:enumTypes 下,编译器现在正确地实现了标准行为。

C++ 标准指定,在没有固定基础类型的枚举定义中,由初始值设定项确定枚举器的类型。 或者,对于没有初始值设定项的枚举器,由上一个枚举器的类型确定(考虑到溢出)。 以前,此类枚举器始终被赋予枚举的推导类型,带基础类型的占位符(通常为 int)。

启用 /Zc:enumTypes 选项后,它是一个潜在的源和二进制中断性变更。 默认情况下,它处于关闭状态,并且不会由 /permissive- 启用,因为该修复可能会影响二进制兼容性。 启用符合性修复后,一些枚举类型会改变大小。 一些 Windows SDK 标头包括此类枚举定义。

示例

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

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

在此示例中,枚举器 A 的类型 char 应位于枚举的右大括号之前,因此 B 应使用 sizeof(char) 进行初始化。 在 /Zc:enumTypes 修复之前,A 的枚举类型为 Enum,推导的基本类型为 intB 使用 sizeof(Enum) 进行了初始化,或者是 4。

Visual Studio 2022 版本 17.3 中的符合性改进

Visual Studio 2022 版本 17.3 包含 Microsoft C/C++ 编译器的以下符合性改进、bug 修复和行为变更。

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++ 编译器的以下符合性改进、bug 修复和行为变更。

未终止的双向字符警告

Visual Studio 2022 版本 17.2 为注释和字符串中未终止的 Unicode 双向字符添加了级别 3 警告 C5255。 该警告解决了特洛伊木马来源:不可见的漏洞(作者:Nicholas Boucher 和 Ross Anderson)中所述的安全问题。 有关 Unicode 双向字符的详细信息,请参阅 Unicode® 标准附录 9:UNICODE 双向算法

警告 C5255 仅处理转换后包含 Unicode 双向字符的文件。 此警告适用于 UTF-8、UTF-16 和 UTF-32 文件,因此必须提供正确的源编码。 这是一项源中断性变更。

示例(之前/之后)

在 Visual Studio 2022 版本 17.2 之前的 Visual Studio 版本中,未终止的双向字符未生成警告。 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 tiebreaker

Visual Studio 2022 版本 17.2 修复了生成错误结果的 <charconv> from_chars() float tiebreaker 规则中的一个 bug。 此 bug 影响了在一个狭窄范围内正好位于连续 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 的行为(在 __STDC__1 时不公开 POSIX 函数),所以在不对稳定语言版本引入中断性变更的情况下,不可能默认为 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 之前的版本中,该警告未处理匿名 structunion 的情况。

这是一项源中断性变更。 它在启用默认警告 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++ 编译器的以下符合性改进、bug 修复和行为变更。

在非局部 lambda 表达式中检测格式错误的捕获默认值

C++ 标准仅允许块范围中的 Lambda 表达式具有捕获默认值。 在 Visual Studio 2022 版本 17.1 及更高版本中,编译器会检测非本地 Lambda 表达式中不允许使用捕获默认值的情况。 它会发出新的级别 4 警告 C5253。

这是一项源中断性变更。 它适用于任何使用新的 Lambda 处理器的模式:/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++ 编译器的以下符合性改进、bug 修复和行为变更。

关于枚举类型位域宽度的警告

将枚举类型的实例声明为位域时,位域的宽度必须容纳枚举的所有可能值。 否则,编译器将发出诊断消息。 请考虑以下示例:

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 规定 N3478 删除了这一疏忽。 此更改已在 MSVC 中实现。 使用 /permissive-(和 /diagnostics:caret)编译示例时,会出现以下错误:

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

此编译器行为是一项源和二进制中断性变更,会影响所有 /std 模式中使用 /permissive- 编译的代码。

另请参阅

Microsoft C/C++ 语言一致性