说明:
Noncompliant Code Example(NCE) :不符合安全编码规则的代码示例
Compliant Solution(CS) :符合安全规则的解决办法
NCE
#include <iostream> struct Widget { explicit Widget(int i) { std::cout << "Widget constructed" << std::endl; } }; struct Gadget { explicit Gadget(Widget wid) { std::cout << "Gadget constructed" << std::endl; } }; void f() { int i = 3; Gadget g(Widget(i)); //被解析为一个函数声明 std::cout << i << std::endl; }
int main(){ return 0; } |
运行结果
3 |
CS
#include <iostream> struct Widget { explicit Widget(int i) { std::cout << "Widget constructed" << std::endl; } }; struct Gadget { explicit Gadget(Widget wid) { std::cout << "Gadget constructed" << std::endl; } }; void f() { int i = 3; Gadget g1((Widget(i))); // Use extra parentheses Gadget g2{Widget(i)}; // Use direct initialization std::cout << i << std::endl; } int main(){ return 0; } |
运行结果
Widget constructed Gadget constructed Widget constructed Gadget constructed 3 |
NCE
// file.h #ifndef FILE_H #define FILE_H class Car { int numWheels; public: Car() : numWheels(4) {} explicit Car(int numWheels) : numWheels(numWheels) {} int get_num_wheels() const { return numWheels; } }; #endif // FILE_H // file1.cpp #include "file.h" #include <iostream> extern Car c; static int numWheels = c.get_num_wheels(); int main() { std::cout << numWheels << std::endl; } // file2.cpp #include "file.h" Car get_default_car() { return Car(6); } Car c = get_default_car(); |
运行结果
如果g++ -std=c++11 -o test file1.cpp file2.cpp,那么结果是0.即调用c.get_num_wheels()时,对象c中的数据成员还没有被初始化。
如果g++ -std=c++11 -o test file2.cpp file1.cpp,那么结果是6.结果和编译顺序相关了。
|
CS
The global object c is initialized before execution of main() begins, so by the time get_num_wheels() is called, c is guaranteed to have already been dynamically initialized.
将调用放到main中,此时外部全局对象已经完成初始化。
// file.h #ifndef FILE_H #define FILE_H class Car { int numWheels; public: Car() : numWheels(4) {} explicit Car(int numWheels) : numWheels(numWheels) {} int get_num_wheels() const { return numWheels; } }; #endif // FILE_H // file1.cpp #include "file.h" #include <iostream> int &get_num_wheels() { extern Car c; static int numWheels = c.get_num_wheels(); return numWheels; } int main() { std::cout << get_num_wheels() << std::endl; } // file2.cpp #include "file.h" Car get_default_car() { return Car(6); } Car c = get_default_car(); |
Car c是不是应该默认初始化,然后是赋值操作?
运行环境中,Car c直接由explicit构造。不是先默认初始化,然后赋值。Car c = get_default_car(); 这个直接由explicit构造函数构造了。
However, the C++ Standard, [except.handle], paragraph 15 [ISO/IEC 14882-2014], in part, states
the following:
The currently handled exception is rethrown if control reaches the end of a handler of
the function-try-block of a constructor or destructor.
C++标准说异常会如果能到达构造、析构函数的最后,那么会重新抛出。
以下假如Bad类在析构时可能会抛出异常,Bad的代码又改不了,那么我们的类怎么修改这个安全风险。
NCE
class SomeClass { Bad bad_member; public: ~SomeClass() { try { // ... } catch(...) { // Handle the exception thrown from the Bad destructor. } } }; |
CS
class SomeClass { Bad bad_member; public: ~SomeClass() { try { // ... } catch(...) { // Catch exceptions thrown from noncompliant destructors of // member objects or base class subobjects. // NOTE: Flowing off the end of a destructor function-try-block // causes the caught exception to be implicitly rethrown, but // an explicit return statement will prevent that from // happening. return; } } }; |
在某头文件中定义了匿名的命名空间,且包含变量或函数。那么包含这个头文件的编译单元中,将各自生成这些变量或函数的实例。
前向声明中会引用不完全类型的对象,但是要注意:
不要尝试删除指向不完整类类型的对象的指针;
不要尝试转换指向不完整类类型的对象的指针;
If the object being deleted has incomplete class type at the point of deletion and the complete class has a non-trivial destructor or a deallocation function, the behavior is undefined.
NCE
class Handle { class Body *impl; // Declaration of a pointer to an incomplete class public: ~Handle() { delete impl; } // Deletion of pointer to an incomplete class // ... }; |
CS
In this compliant solution, the deletion of impl is moved to a part of the code where Body is
defined.
如果有前向声明,且有释放不完整类,那么释放的定义在不完整类定义的后面。
class Handle { class Body *impl; // Declaration of a pointer to an incomplete class public: ~Handle(); // ... }; // Elsewhere class Body { /* ... */ }; Handle::~Handle() { delete impl; } |
不要用空指针创建string
NCE
#include <cstdlib> #include <string> void f() { std::string tmp(std::getenv("TMP")); if (!tmp.empty()) { // ... } } |
CS
#include <cstdlib> #include <string> void f() { const char *tmpPtrVal = std::getenv("TMP"); std::string tmp(tmpPtrVal ? tmpPtrVal : ""); if (!tmp.empty()) { // ... } } |
和vector insert一样,指针可能因为扩容而变更。
NCE
#include <string> void f(const std::string &input) { std::string email; // Copy input into email converting ";" to " " std::string::iterator loc = email.begin(); for (auto i = input.begin(), e = input.end(); i != e; ++i, ++loc) { email.insert(loc, *i != ‘;‘ ? *i : ‘ ‘); } } |
CS
#include <string> void f(const std::string &input) { std::string email; // Copy input into email converting ";" to " " std::string::iterator loc = email.begin(); for (auto i = input.begin(), e = input.end(); i != e; ++i, ++loc) { loc = email.insert(loc, *i != ‘;‘ ? *i : ‘ ‘); } } |
下标引用需要检查范围
NCE
#include <string> extern std::size_t get_index(); void f() { std::string s("01234567"); s[get_index()] = ‘1‘; } |
CS
#include <stdexcept> #include <string> extern std::size_t get_index(); void f() { std::string s("01234567"); try { s.at(get_index()) = ‘1‘; } catch (std::out_of_range &) { // Handle error } } |
局部变量在出if就释放了
NCE
#include <iostream> #include <memory> #include <cstring> int main(int argc, const char *argv[]) { const char *s = ""; if (argc > 1) { enum { BufferSize = 32 }; try { std::unique_ptr<char[]> buff(new char[BufferSize]); std::memset(buff.get(), 0, BufferSize); // ... s = std::strncpy(buff.get(), argv[1], BufferSize - 1); } catch (std::bad_alloc &) { // Handle error } } std::cout << s << std::endl; } |
CE
#include <iostream> #include <memory> #include <cstring> int main(int argc, const char *argv[]) { std::unique_ptr<char[]> buff; const char *s = ""; if (argc > 1) { enum { BufferSize = 32 }; try { buff.reset(new char[BufferSize]); std::memset(buff.get(), 0, BufferSize); // ... s = std::strncpy(buff.get(), argv[1], BufferSize - 1); } catch (std::bad_alloc &) { // Handle error } } std::cout << s << std::endl; } |
CS
#include <thread> void throwing_func() noexcept(false); void thread_start(void) { try { throwing_func(); } catch (...) { // Handle error } } void f() { std::thread t(thread_start); t.join(); } |
When an exception is caught by a function-try-block handler in a constructor, any fully
constructed base classes and class members of the object are destroyed prior to entering the
handler.
构造函数用try-block处理异常之前,类中成员对象已经销毁了。异常处理函数中不要操作这些数据成员。
NCE
#include <string> class C { std::string str; public: C(const std::string &s) try : str(s) { // ... } catch (...) { if (!str.empty()) { // ... } } }; |
CS由深到浅
class B {}; class D : public B {}; void f() { try { // ... } catch (D &d) { // ... } catch (B &b) { // ... } } |
禁止在构造函数或析构函数中使用虚函数,在构造期间尝试从基类中调用子类的函数有风险的。此时,子类还没有完成初始化。
子类继续父类,子类可能增加其他变量。如果以值的方式赋值或拷贝子类对象给父类对象,则附加的信息丢失。禁止使用子类对象初始化一个父类对象,除非通过引用,指针。
NCE
#include <iostream> #include <string> class Employee { std::string name; protected: virtual void print(std::ostream &os) const { os << "Employee: " << get_name() << std::endl; } public: Employee(const std::string &name) : name(name) {} const std::string &get_name() const { return name; } friend std::ostream &operator<< (std::ostream &os, const Employee &e) { e.print(os); return os; } }; class Manager : public Employee { Employee assistant; protected: void print(std::ostream &os) const override { os << "Manager: " << get_name() << std::endl; os << "Assistant: " << std::endl << "\t" << get_assistant() << std::endl; } public: Manager(const std::string &name, const Employee &assistant) : Employee(name), assistant(assistant) {} const Employee &get_assistant() const { return assistant; } }; void f(Employee e) { std::cout << e; } int main() { Employee coder("Joe Smith"); Employee typist("Bill Jones"); Manager designer("Jane Doe", typist); f(coder); f(typist); f(designer); } |
CE
// Remainder of code unchanged... void f(const Employee *e) { if (e) { std::cout << *e; } } int main() { Employee coder("Joe Smith"); Employee typist("Bill Jones"); Manager designer("Jane Doe", typist); f(&coder); f(&typist); f(&designer); } |
当父类没有定义虚析构函数时,尝试使用指向父类的指针删除子类对象将导致未定义行为。
原文:https://www.cnblogs.com/sunnypoem/p/12189121.html