锈和C++是两种流行的系统编程语言。 多年来,C++的焦点一直是性能方面的问题。 W 我们的听力越来越差 客户和安全研究人员的呼吁,C++应该具有更强的语言安全保证。 在编程安全方面,C++经常会生锈。 Visual Studio 2019版本16.7 包含 四条新规定 在C++核心检查中 将锈蚀的安全特性融入C++ .
更多详细信息 C++核心检查 ,请看 C++核心指南检查器引用 文档。 如果您刚刚开始使用本机代码分析工具,请看一下我们的介绍 C/C++代码分析的快速启动 .
RISE的模式匹配构造可以类似于C++使用。 switch
声明。然而,它们不同的一种方式是,锈当量要求程序员覆盖所有可能的p 模式匹配。这可以通过为每个对象编写显式处理程序来实现 图案或 为未明确涵盖的案例追加默认处理程序。
例如,如果默认处理程序 是 失踪 g。
// i32 == 32-bit signed integer fn printDiceRoll(roll: i32) { match roll { 1 => println!("one!"), 2 => println!("two!"), 3 => println!("three!"), 4 => println!("four!"), 5 => println!("five!"), 6 => println!("six!"), _ => println!("what kind of dice are you using?") // default handler } }
这是一个整洁的小安全功能,因为它可以防止这种情况 非常容易 使,但不是那么容易抓住,progr 安明误差。
VisualStudio会在出现 enum
类型不包含在C++切换语句
本次发布 引入一个新的检查,以在switch语句切换到非- 枚举 类型(即。, char
, int
,…)缺少 default
标签。 你可以在这张支票上找到详细的文件 在这里 . 要在VisualStudio中启用此规则,必须为项目选择规则集“C++核心检查样式规则”、“C++核心检查规则”或“Microsoft所有规则”,然后运行代码分析。
重写 g以上C++中的锈迹示例, 我们会的 得到像这样的东西 在下面。
void printDiceRoll(int roll) { switch (roll) { case 1: std::cout << "one"; break; case 2: std::cout << "two"; break; case 3: std::cout << "three"; break; case 4: std::cout << "four"; break; case 5: std::cout << "five"; break; case 6: std::cout << "six"; break; default: std::cout << "what kind of dice are you using?"; break; } }
移除t 他 default
处理程序现在会产生一个警告 .
void printDiceRoll(int roll) { switch (roll) { // warning C26818: Switch statement does not cover all cases. Consider adding a 'default' label (es.79) case 1: std::cout << "one"; break; case 2: std::cout << "two"; break; case 3: std::cout << "three"; break; case 4: std::cout << "four"; break; case 5: std::cout << "five"; break; case 6: std::cout << "six"; break; } }
生锈的另一个限制 match
声明是它不支持 fallthrough
在案件之间。在C++中,另一方面,下面的代码是完全有效的 .
enum class Food { BANANA, ORANGE, PIZZA, CAKE, KALE, CELERY }; void takeFromFridge(Food food) { switch (food) { case Food::BANANA: case Food::ORANGE: peel(food); // implicit fallthrough case Food::PIZZA: case Food::CAKE: eat(food); break; case Food::KALE: case Food::CELERY: throwOut(food); break; } }
虽然这个例子很好,但很含蓄 失败 两种情况之间可以很容易地是一个b ug公司。比如说,上面函数的程序员忘记了 break
通话后的声明 eat(food)
. 代码将运行,但行为将完全不正确。使用更大更复杂的代码库,跟踪这个 错误的类型可以是 困难的 .
幸运的是,用C++ 17增加了 [[fallthrough]] 注释,其目的是标记 失败 在大小写标签之间,比如在上面的例子中,这样代码的维护者就可以确信 e 失败 行为是有意的。
与 Visual Studio 2019版本16.7 ,警告C26819 当一个非空的开关箱掉入下一个箱而没有标记 失败 使用 [[fallthrough]] 注释。详细do 文件可以找到 在这里 . 在visualstudio中默认启用此规则 运行代码分析时。
void takeFromFridge(Food food) { switch (food) { case Food::BANANA: // empty case, fallthrough annotation not needed case Food::ORANGE: peel(food); // warning C26819: Unannotated fallthrough between switch labels (es.78) case Food::PIZZA: // empty case, fallthrough annotation not needed case Food::CAKE: eat(food); break; case Food::KALE: // empty case, fallthrough annotation not needed case Food::CELERY: throwOut(food); break; } }
若要修复此警告,请插入 [[fallthrough]]
陈述 .
void takeFromFridge(Food food) { switch (food) { case Food::BANANA: case Food::ORANGE: peel(food); [[fallthrough]]; // the fallthrough is intended case Food::PIZZA: case Food::CAKE: eat(food); break; case Food::KALE: case Food::CELERY: throwOut(food); break; } }
Rust和C++的主要区别是锈是M。 默认为ove而不是copy。
一些防锈代码:
struct MySequence { sequence: Vec<i32> } fn main() { let mut a = MySequence { sequence: vec![0, 1, 1, 2, 3, 5, 8, 13] }; let mut _b = a; a.sequence.push(21); // error! `a` was moved into `b` and can no longer be used }
这意味着在大多数情况下,无论何时需要复制,都必须使用显式的复制语义。
#[derive(Clone)] struct MySequence { sequence: Vec<i32> } fn main() { let mut a = MySequence { sequence: vec![0, 1, 1, 2, 3, 5, 8, 13] }; let mut _b = a.clone(); a.sequence.push(21); // much better }
另一方面,C++默认是复制。这不是一个问题 一般但是 有时可能是bug的来源。一种情况是 通常在语句的范围内发生。以下面的代码为例 .
struct Person { std::string first_name; std::string last_name; std::string email_address; }; void emailEveryoneInCompany(const std::vector<Person>& employees) { Email email; for (Person p: employees) { // copy of type `Person` occurs on each iteration email.addRecipient(p.email_address); } email.addBody("Sorry for the spam"); email.send(); }
在上面的代码片段中,向量的每个元素都被复制到 p
在循环的每次迭代中。这一点并不明显,如果拷贝成本很高,这可能是导致效率低下的一个重要原因。为了弥补这个不必要的副本, 我们补充道 一个新的 C++核心检查规则 ,建议删除副本的方法 .
void emailEveryoneInCompany(const std::vector<Person>& employees) { Email email; for (Person p: employees) { // Warning C26817: Potentially expensive copy of variable 'p' in range-for loop. Consider making it a const reference (es.71) email.addRecipient(p.email_address); } email.addBody("Sorry for the spam"); email.send(); }
通过使用警告中的建议并更改变量的类型 p
在 从a循环 Person
到 const Person&
,变量不再在每次迭代时接收昂贵的数据副本。
void emailEveryoneInCompany(const std::vector<Person>& employees) { Email email; for (const Person& p: employees) { // expensive copy no longer occurs email.addRecipient(p.email_address); } email.addBody("Sorry for the spam"); email.send(); }
为了决定什么是“昂贵的”拷贝,che使用以下启发式方法 ck公司:
如果类型的大小大于平台相关指针大小的两倍,并且类型不是智能指针或 gsl
::span
, gsl :: string_span
,或 std::
string_view
,那么这个拷贝就被认为是昂贵的。这意味着对于小型 数据类型 如数字标量,则不会触发警告。对于较大的类型,例如 Person
在上面的例子中输入,副本被认为是昂贵的,并会发出警告。
最后一点要注意的是,如果t 这个变量在循环体中发生了变异 .
struct Person { std::string first_name; std::string last_name; int hourlyrate; // dollars per hour }; void giveEveryoneARaise(const std::vector<Person>& employees) { for (Person p: employees) { p.hourlyrate += 10; // `p` can no longer be marked `const Person&`, so the copy is unavoidable } }
如果容器 不是 const限定,则可以通过更改类型来避免复制 Person
到 Perso
n&
.
void giveEveryoneARaise() { std::vector<Person> employees = getEmployees(); for (Person& p: employees) { // no more expensive copying, but any subsequent mutation will change the container! p.hourlyrate += 10; } }
但这种变化伴随着代码引入了新的副作用。因此,复制警告的范围仅建议 标记 循环变量为 const T&
,和 如果无法合法标记循环变量,则不会激发 const
.
可以找到支票的完整文件 在这里 . 在visualstudio中默认启用此规则 运行代码分析时。
此版本中的最后一个新检查涉及昂贵的副本 发生 机智 h使用 auto
类型。
考虑下面的例子,在这个例子中,被指定引用的变量发生类型解析。
struct PasswordManager { password: String } impl PasswordManager { // member-access function returning an immutable reference to a member fn getPassword(&self) -> &String { &self.password } // Note: for the sake of an example dealing with expensive types, a &String is being returned. // More realistically though, a string slice would be returned instead (similar to a string view in C++) } fn stealPassword(pm: &PasswordManager) { let password = pm.getPassword(); // the type of `a` resolves to `&String`. No copy occurs. }
因为生锈的要求 这个 大多数 案例 复制必须是显式的,类型为 password
在本例中,当分配一个不可变引用时,自动解析为一个不可变引用,并且不执行昂贵的复制。
另一方面,考虑 C++代码 .
class PasswordManager { std::string password; public: const std::string& getPassword() const { return password; } }; void stealPassword(const PasswordManager& pm) { auto password = pm.getPassword(); // the type of `password` resolves to `std::string`. Copy occurs. }
这里是 password
决心 std::
string
,即使 getPassword
()
是一个 常量引用 一根绳子。其结果是 Pas swordManager ::
password
复制到局部变量中 password
.
将其与返回指针的函数进行比较:
class PasswordManager { std::string password; public: const std::string* getPassword() const { return &password; } }; void stealPassword(const PasswordManager& pm) { auto password = pm.getPassword(); // the type of `password` resolves to `const std::string*`. No copy occurs. }
指定参照和点之间的行为差异 对标记为 auto
不明显,导致可能不需要和意外的复制。
为了防止这种行为引起的错误,检查器检查所有初始化实例,从对标记为的变量的引用开始 auto
. 如果使用与要检查的范围相同的启发式方法,结果副本被认为是昂贵的,则检查器警告要标记变量 const auto&
相反。
class PasswordManager { std::string password; public: const std::string& getPassword() const { return password; } }; void stealPassword(const PasswordManager& pm) { auto password = pm.getPassword(); // Warning C26820: Assigning by value when a const-reference would suffice, use const auto& instead (p.9) }
A 与要检查的范围一样,当变量不能合法标记时,不会引发此警告 const
.
std::string hashPassword(const PasswordManager& pm) { auto password = pm.getPassword(); // warning no longer gets raised because `password` is modified below password += "salt"; return std::hash(password); }
另一个不会引发警告的实例是,只要引用是从临时引用派生的。在这种情况下,使用 const a
uto&
将 一旦临时文件被销毁,就会产生一个悬空引用。
class PasswordManager { std::string password; public: PasswordManager(const std::string& password): password(password) {} const std::string& getPassword() const { return password; } }; void stealPassword() { const auto& password = PasswordManager("123").getPassword(); // using `const auto&` instead of just `auto` use_password(password); // `password` is now referencing invalid memory! }
可以找到支票的完整文件 在这里 . 在visualstudio中默认启用此规则 运行代码分析时。
查看这些新添加的规则和最近发布的 GSL 3.0级 图书馆 告诉我们他们 帮助 你写的更安全的C++。请继续关注我们的安全措施 规则 在VisualStudio的未来版本中。
下载 Visual Studio 2019版本16.7 今天就来试试。 我们会的 很高兴收到您的来信,帮助我们确定优先级并为您构建合适的功能。我们可以通过以下评论联系到您, 开发者社区 , 还有推特( @视觉 ). 提交bug或建议特性的最佳方法是通过开发者社区。