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

Visual Studio 中的 Microsoft C++ (MSVC) 在每个版本中进行了符合性改进和 bug 修复。 本文按主要版本然后次要版本列出了改进之处。 若要直接跳转到特定版本的更改,请使用本文下面的列表。

本文档列出了 Visual Studio 2022 中的更改。 有关 Visual Studio 2019 中的更改的指南,请参阅 Visual Studio 2019 中的 C++ 符合性改进。 若要了解 Visual Studio 2017 中的更改,请参阅 Visual Studio 2017 中的 C++ 一致性改进。 有关之前符合性改进的完整列表,请参阅 Visual C++ 2003 - 2015 中的新增功能

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

Visual Studio 2022 版本 17.2 包含 Microsoft 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.009765625131071.98828125。)tiebreaker 规则希望舍入到“偶数”,“偶数”刚好是“向下”舍入,但实现却错误地“向上”舍入。 (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++ 编译器的以下符合性改进、bug 修复和行为更改。

检测非本地 Lambda 表达式中格式错误的捕获默认值

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

这是一项源中断性变更。 它适用于任何使用新的 Lambda 处理器的模式:/Zc:lambda/std:c++20/std:c++latest

示例

在 Visual C++ 2022 版本 17.1 中,此代码现在会发出错误:

#pragma warning(error:5253)

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

// capture_default.cpp(3,14): error C5253: a non-local 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 在一个函数模板的主体内(或在一个类模板的成员函数的主体内),编译器就不会执行这种分析。

这是一项源中断性变更。 它适用于任何表示 /Zc: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++ 编译器的以下符合性改进、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++ 语言一致性