Visual Studio 2019 Preview 2中的生存期配置文件更新

C++核心指南的生命周期配置文件,它是 C++核心指南 其目的是检测C++代码中的悬空指针和引用等生命周期问题。它使用源代码中已经存在的类型信息以及函数之间的一些简单契约,在编译时以最少的注释检测缺陷。

null

这些是概要文件希望代码遵循的基本契约:

  1. 不要使用可能悬空的指针。
  2. 不要将可能悬空的指针传递给另一个函数。
  3. 不要从任何函数返回可能悬空的指针。

有关配置文件的历史记录和目标的更多信息,请查看 Herb Sutter关于1.0版的博客文章 .

Visual Studio 2019预览版2中的新增功能

在Preview2中,我们发布了一个生命周期概要文件检查器的预览版本,它实现了 生命周期配置文件的发布版本 . 此检查器是 C++核心检查程序 在Visual Studio中。

  • 支持迭代器、字符串视图和跨度。
  • 更好地检测自定义所有者和指针类型,允许行为类似于容器、拥有指针或不拥有指针的自定义类型参与分析。
  • 函数调用前和后条件的类型感知默认规则有助于减少误报并提高准确性。
  • 更好地支持聚合类型。
  • 一般正确性和性能改进。
  • 一些简单的空ptr分析。

启用生存期配置文件检查器规则

默认情况下不启用检查器规则。如果要尝试新规则,则必须更新为项目选择的代码分析规则集。您可以选择“C++核心检查生存期规则”(仅启用生存期配置文件规则),也可以修改现有规则集以启用警告26486到26489。

Screenshot of the Code Analysis properties page that shows the C++ Core Check Lifetime Rules ruleset selected.
代码分析属性页的屏幕截图,显示了C++核心检查生命周期规则规则集的选择。

运行代码分析时(分析>运行代码分析)错误列表中将出现警告,或者如果启用了后台代码分析,则生存期错误将在编辑器中以绿色曲线显示。

Screenshot showing a Lifetime Profile Checker warning with a green squiggle in source code.
显示源代码中带有绿色波形的终身配置文件检查器警告的屏幕截图。

示例

悬挂指针

最简单的例子——使用悬空指针——是最好的开始。在这里 px 指向 x 然后 x 离开范围离开 px 晃来晃去的。什么时候? px 则发出警告。

void simple_test()
{
    int* px;
    {
        int x = 0;
        px = &x;
    }
    *px = 1; // error, dangling pointer to 'x'
}

悬挂输出指针

也不允许返回悬空指针。在本例中,参数 ppx 假定为输出参数。在本例中,它被设置为指向 x 在函数末尾超出了范围。这就离开了 *ppx 晃来晃去的。

void out_parameter(int x, int** ppx)  // *ppx points to 'x' which is invalid
{
    *ppx = &x;
}

悬垂线视图

最后两个例子很明显,但临时实例可能会引入一些微妙的bug。你能在下面的代码中找到错误吗?

std::string get_string();
void dangling_string_view()
{
    std::string_view sv = get_string();
    auto c = sv.at(0);
}

在本例中,字符串视图 sv 使用从返回的临时字符串实例构造 get_string() . 然后销毁临时字符串,使字符串视图引用无效对象。

悬挂迭代器

在容器中使用无效迭代器时,会出现另一个难以发现的生存期问题。在下面的例子中,调用 push_back 可能导致向量重新分配其底层存储,从而使迭代器无效 it .

void dangling_iterator()
{
    std::vector<int> v = { 1, 2, 3 };
    auto it = v.begin();
    *it = 0; // ok, iterator is valid
    v.push_back(4);
    *it = 0; // error, using an invalid iterator
}

关于这个例子需要注意的一点是,“std::vector::push u back”没有特殊处理。此行为不符合默认配置文件规则。 一个规则将容器分类为“所有者”。然后,当对所有者调用一个非常量方法时,它所拥有的内存被假定为无效,指向所拥有内存的迭代器也被认为无效。

修改的所有者

该简介在其指导中是规定性的。它期望您的代码在定义函数参数时习惯性地使用类型系统。在下一个例子中, std::unique_ptr “Owner”类型通过非常量引用传递给另一个函数。根据概要文件的规则,通过非常量引用传递的所有者假定由被调用方修改。

void use_unique_ptr(std::unique_ptr<int>& upRef);
void assumes_modification()
{
    auto unique = std::make_unique<int>(0); // Line A
    auto ptr = unique.get();
    *ptr = 10; // ok, ptr is valid
    use_unique_ptr(unique);
    *ptr = 10; // error, dangling pointer to the memory held by 'unique' at Line A
}

在这个例子中,我们得到一个原始指针, ptr ,到拥有的内存 unique . 那么 unique 传递给函数 use_unique_ptr 通过非常量引用。因为这是对 unique 如果函数可以做任何事情,分析假设 unique ‘以某种方式无效(例如,unique ptr::reset),这将导致 ptr 悬挂。

更多示例

分析还可以发现许多其他情况。在visualstudio中对自己的代码进行测试,看看您发现了什么。还可以结账 赫伯的博客 更多的例子,如果你好奇的话,请通读一生简介。

已知问题

当前的实现并不完全支持生命周期概要文件中描述的分析。以下是本版本中未实现的大类。

  • 注释 –本文介绍了注释(即。 [[gsl::lifetime-const]] )不支持的。实际上,这意味着,如果默认的分析规则不适用于您的代码,那么除了 抑制 假阳性。
  • 例外情况 –异常处理路径,包括 catch 块,当前未分析。
  • 默认规则 r STL类型 –代替 lifetime-const 注释,本文建议对于我们想要覆盖默认值的罕见STL容器成员函数,我们将其视为注释。例如,一个 std::vector::at 不是 const 因为它可以返回一个非常量引用—但是我们知道调用它是 lifetime-const 因为它不会使向量的内存失效。我们还没有完成对所有STL容器类型进行隐式注释的工作。
  • Lambda捕获 –如果堆栈变量是通过lambda中的引用捕获的,我们当前不会检测lambda是否离开捕获变量的作用域。
    auto lambda_test()
    {
        int x;
        auto captures_x = [&x] { return x; };
        return captures_x; // returns a dangling reference to 'x'
    }

总结

在visualstudio2019preview 2中试用Lifetime Profile Checker。我们希望它能帮助您识别项目中的生存期问题。如果您发现假阳性或假阴性,请报告它们,以便我们可以优先考虑对您重要的场景。如果您对此检查或任何Visual Studio功能有任何建议或问题,请 报告问题 或张贴在 开发者社区 告诉我们。我们还在推特上 @视觉 .

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享