写点什么

C++ 最佳实践 | 3. 安全性

作者:俞凡
  • 2022 年 5 月 14 日
  • 本文字数:2009 字

    阅读完需:约 7 分钟

本系列是开源书C++ Best Practises的中文版,全书从工具、代码风格、安全性、可维护性、可移植性、多线程、性能、正确性等角度全面介绍了现代 C++项目的最佳实践。本文是该系列的第三篇。

安全性

尽量使用 const

const修饰变量或方法,从而告诉编译器这些都是不可变的,有助于编译器优化代码,并帮助开发人员了解函数是否有副作用。此外,使用const &可以防止编译器复制不必要的数据。John Carmack对const的评论值得一读。


// Bad Ideaclass MyClass{public:  void do_something(int i);  void do_something(std::string str);};

// Good Ideaclass MyClass{public: void do_something(const int i); void do_something(const std::string &str);};
复制代码

仔细考虑返回类型

  • Getters(成员变量读取 API)

  • 正常情况下,通过返回值读取成员变量时,使用&const &返回值可以显著提高性能

  • 按值返回更有利于线程安全,如果返回的值就是为了复制使用,就不会有性能损耗

  • 如果 API 返回值使用协变类型(covariant return types),必须返回&*

  • 临时值和局部值

  • 始终按值返回


参考:


https://github.com/lefticus/cppbestpractices/issues/21


https://twitter.com/lefticus/status/635943577328095232

不要用 const 引用传递和返回简单类型

// Very Bad Ideaclass MyClass{public:  explicit MyClass(const int& t_int_value)    : m_int_value(t_int_value)  {  }
const int& get_int_value() const { return m_int_value; }
private: int m_int_value;}
复制代码


相反,通过值传递和返回简单类型。如果不打算更改传递的值,请将它们声明为const,但不要声明为const引用:


// Good Ideaclass MyClass{public:  explicit MyClass(const int t_int_value)    : m_int_value(t_int_value)  {  }
int get_int_value() const { return m_int_value; }
private: int m_int_value;}
复制代码


为什么要这样?因为通过引用传递和返回会导致指针操作,而值传递在处理器寄存器中处理,速度更快。

避免访问裸内存

C++中很难在没有内存错误和泄漏风险的情况下正确处理裸内存的访问、分配和回收,C++11 提供了避免这些问题的工具。


// Bad IdeaMyClass *myobj = new MyClass;
// ...delete myobj;

// Good Ideaauto myobj = std::make_unique<MyClass>(constructor_param1, constructor_param2); // C++14auto myobj = std::unique_ptr<MyClass>(new MyClass(constructor_param1, constructor_param2)); // C++11auto mybuffer = std::make_unique<char[]>(length); // C++14auto mybuffer = std::unique_ptr<char[]>(new char[length]); // C++11
// or for reference counted objectsauto myobj = std::make_shared<MyClass>();
// ...// myobj is automatically freed for you whenever it is no longer used.
复制代码

std::arraystd::vector代替 C 风格的数组

这两种方法都保证了对象的连续内存布局,并且可以(而且应该)完全取代 C 风格数组,另外这也是不使用裸指针的诸多原因之一。


另外,避免使用std::shared_ptr保存数组

使用异常

返回值(例如boost::optional),可以被忽略,如果不检查,可能会导致崩溃或内存错误,而异常不能被忽略。另一方面,异常可以被捕获和处理。可能异常会一直上升到应用程序的最高层级被捕获、记录到日志中,并触发应用自动重启。


C++的设计者之一 Stroustrup 谈论过这个话题: Why use exceptions?

用 C++风格的类型转换,而不是 C 风格的类型转换

用 C++风格的强制类型转换(static_cast<>dynamic_cast<>,…)代替 C 风格的强制类型转换,C++风格的强制转换允许更多的编译器检查,而且相当安全。


// Bad Ideadouble x = getX();int i = (int) x;
// Not a Bad Ideaint i = static_cast<int>(x);
复制代码


此外,C++类型转换风格更为显式,对搜索更为友好。


但如果需要将double类型转换为int类型,请考虑重构程序逻辑(例如,对溢出和下溢进行额外检查)。避免出现测量了 3 次,然后切割 0.9999999999981 次这种情况。

不要定义可变参数函数(variadic function)

可变参数函数可以接受数量可变的参数,最著名的例子可能是printf()。虽然可以定义此类函数,但可能存在安全风险。可变参数函数的使用不是类型安全的,错误的输入参数可能导致程序以未定义的行为终止。这种未定义的行为可能会导致安全问题。如果使用支持 C++1 的编译器,那么可以使用可变参数模板。


参考: It is technically possible to make typesafe C-style variadic functions with some compilers

额外资源

David Wheeler 的《How to Prevent The Next Heartbleed》一书很好的分析了代码安全的现状以及如何确保代码安全。


你好,我是俞凡,在 Motorola 做过研发,现在在 Mavenir 做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI 等技术始终保持着浓厚的兴趣,平时喜欢阅读、思考,相信持续学习、终身成长,欢迎一起交流学习。

微信公众号:DeepNoMind

发布于: 刚刚阅读数: 3
用户头像

俞凡

关注

公众号:DeepNoMind 2017.10.18 加入

俞凡,Mavenir Systems研发总监,关注高可用架构、高性能服务、5G、人工智能、区块链、DevOps、Agile等。公众号:DeepNoMind

评论

发布
暂无评论
C++最佳实践 | 3. 安全性_c++_俞凡_InfoQ写作社区