重磅!《安全规则集合》正式发布 https://mp.weixin.qq.com/s/_AX2dOte7QKMYQg3UjN4bQ

safe-rules/c-cpp-rules.md at main · Qihoo360/safe-rules https://github.com/Qihoo360/safe-rules/blob/main/c-cpp-rules.md

C/C++安全规则集合 Version

Bjarne Stroustrup: “C makes it easy to shoot yourself in the foot; C++ makes it harder, but when you do it blows your whole leg off.

  针对C、C++语言,本文收录了409种需要重点关注的问题,可为制定编程规范提供依据,也可为代码审计以及相关培训提供指导意见,适用于桌面、服务端以及嵌入式等软件系统。
  每个问题对应一条规则,每条规则可直接作为规范条款或审计检查点,本文是适用于不同应用场景的规则集合,读者可根据自身需求从中选取某个子集作为规范或审计依据,从而提高软件产品的安全性。

规则说明

规则按如下主题分为15个类别:

  1. Security:敏感数据保护、攻击防御等问题
  2. Resource:资源分配、使用与回收
  3. Precompile:预处理指令、宏、注释等问题
  4. Global:全局及命名空间作用域相关问题
  5. Type:类型相关的设计与实现
  6. Declaration:声明
  7. Exception:异常
  8. Function:函数实现
  9. Control:流程控制
  10. Expression:表达式
  11. Literal:常量
  12. Cast:类型转换
  13. Buffer:缓冲区
  14. Pointer:指针
  15. Style:样式、风格等问题

每条规则包括:

  • 编号:规则在本文中的章节编号,以“R”开头,称为Section-ID
  • 名称:用简练的短语描述违反规则的状况,以“ID_”开头,称为Fault-ID
  • 标题:规则的定义
  • 说明:规则设立的原因、示例、违反规则的后果、改进建议、参照依据、参考资料等内容

当代码出现违反规则的情况时,可分为:

  • Error:可直接导致错误的问题
  • Warning:可导致错误或存在隐患的问题
  • Suspicious:代码的可疑形式
  • Suggestion:对提高代码质量的建议

规则的说明包含:

  • 示例:规则相关的示例代码,指明符合规则(Compliant)的和违反规则(Non-compliant)的情况
  • 相关:与当前规则有相关性的规则,可作为扩展阅读的线索
  • 依据:规则依照的ISO/IEC标准,C规则以ISO/IEC 9899:2011为主,C++规则以ISO/IEC 14882:2011为主
  • 配置:某些规则的对象可由用户指定,审计工具可以此为参照实现定制化功能
  • 参考:规则参考的其他规范条款,如C++ Core Guidelines、MISRA、CWE、SEI CERT等,也可作为扩展阅读的线索

规则的相关性分为:

  • 特化:设规则A的特殊情况需要由规则B阐明,称规则B是规则A的特化
  • 泛化:与特化相反,称规则A是规则B的泛化
  • 相交:设两个规则针对不同的问题,但在内容上有一定的交集,称这两个规则相交

规则以“标准名称:版本 章节编号(段落编号)-性质”的格式引用标准,如“ISO/IEC 14882:2011 5.6(4)-undefined”,表示引用C++11标准的第5章第6节第4段说明的具有undefined性质的问题。

其中“性质”分为:

  • undefined:一般指某种错误,使程序产生undefined behavior
  • unspecified:标准不作明确规定的情况,由编译器或环境自主定义,具有随意性
  • implementation defined:由实现定义,也是由编译器或环境自主定义,与unspecified不同,要求有明确的文档支持
  • deprecated:已过时或已废弃的用法

本文以ISO/IEC 9899:2011以及ISO/IEC 14882:2011为主要依据,兼顾C18、C++17以及历史标准,没有特殊说明的规则同时适用于C语言和C++语言,只适用于某一种语言的规则会另有说明。

规则选取

本文是适用于不同应用场景的规则集合,读者可选取适合自己需求的规则。

指出某种错误的规则,如有“不可”、“不应”等字样的规则应尽量被选取,有“禁用”等字样的规则可能只适用于某一场景,可酌情选取。

如果将本文作为培训内容,为了全面理解各种场景下存在的问题,应选取全部规则。

规则列表

1. Security

  • R1.1 敏感数据不应以明文形式写入代码
  • R1.2 敏感数据不可被系统外界感知
  • R1.3 敏感数据在使用后应被有效清理
  • R1.4 公共成员或全局对象不应记录敏感数据
  • R1.5 预判由用户输入造成的不良后果
  • R1.6 避免在一个事务中通过路径多次访问同一文件
  • R1.7 访问共享数据应遵循合理的同步机制
  • R1.8 对文件设定合理的权限
  • R1.9 对用户设置合理的权限
  • R1.10 不应引用危险符号名称
  • R1.11 避免调用具有危险性的函数
  • R1.12 不应调用已过时的函数
  • R1.13 禁用不安全的字符串函数
  • R1.14 确保字符串以空字符结尾
  • R1.15 避免使用由实现定义的库函数
  • R1.16 除数不可存在值为0的可能性
  • R1.17 禁用atof、atoi、atol以及atoll等函数
  • R1.18 格式化字符串应为常量
  • R1.19 与程序实现相关的信息不可被外界感知
  • R1.20 不应硬编码IP地址
  • R1.21 避免使用errno

 

2. Resource

  • R2.1 资源管理应遵循面向对象的方法
  • R2.2 不可失去对已分配资源的控制
  • R2.3 不可失去对已分配内存的控制
  • R2.4 不可访问未初始化或已释放的资源
  • R2.5 资源不可被重复释放
  • R2.6 资源的分配与回收方法应配套使用
  • R2.7 用new分配的单个对象应该用delete释放,不应该用delete[]释放
  • R2.8 用new分配的数组应使用delete[]释放,不应使用delete释放
  • R2.9 对象被move之后的值不应再被使用
  • R2.10 对象申请的资源须在析构函数中释放
  • R2.11 如果构造函数抛出异常需确保相关资源没有泄漏
  • R2.12 C++代码中禁用malloc、free等内存分配与回收函数
  • R2.13 在一个语句中最多执行一次显式资源分配
  • R2.14 在栈上分配的空间以及非动态申请的资源不可被释放
  • R2.15 避免使用在栈上分配内存的函数
  • R2.16 避免不必要的内存分配
  • R2.17 避免动态内存分配
  • R2.18 标准FILE对象不应被复制

 

3. Precompile

  • 3.1 Include
    • R3.1.1 字母、数字、下划线以及点号之外的字符不应出现在头文件名称中
    • R3.1.2 include指令中不应使用反斜杠
    • R3.1.3 include指令中不应使用绝对路径
    • R3.1.4 禁用不合规的头文件
    • R3.1.5 在C++代码中不应引用C头文件
  • 3.2 Macro
    • R3.2.1 宏的命名应遵循合理的方式
    • R3.2.2 不可定义具有保留意义的宏名称
    • R3.2.3 不可取消定义具有保留意义的宏名称
    • R3.2.4 可作为子表达式的宏定义应该用括号括起来
    • R3.2.5 宏参数在宏定义的表达式中应该用括号括起来
    • R3.2.6 由多个语句组成的宏定义应该用do-while(0)括起来
    • R3.2.7 宏的实参个数不可小于形参个数
    • R3.2.8 宏的实参个数不可大于形参个数
    • R3.2.9 宏参数不应有副作用
    • R3.2.10 宏参数数量应在规定范围之内
    • R3.2.11 宏名称中不应存在拼写错误
    • R3.2.12 不应使用宏定义常量
    • R3.2.13 不应使用宏定义类型
    • R3.2.14 可由函数实现的功能不应使用宏实现
    • R3.2.15 在C++代码中不应使用宏offsetof
    • R3.2.16 在宏定义中由#修饰的参数后不应出现##
  • 3.3 Directive
    • R3.3.1 头文件不应缺少守卫
    • R3.3.2 不应出现非标准格式的预编译指令
    • R3.3.3 不应使用非标准预编译指令
    • R3.3.4 对编译警告的屏蔽应慎重
    • R3.3.5 代码在高级别的警告设置下编译
  • 3.4 Comment
    • R3.4.1 关注TODO、FIXME、XXX、BUG等特殊注释
    • R3.4.2 注释不可嵌套
    • R3.4.3 注释应出现在合理的位置
  • 3.5 Other
    • R3.5.1 除转义字符、宏定义之外不应使用反斜杠

 

4. Global

  • R4.1 不可修改std命名空间
  • R4.2 非全局命名空间中不应存在以main命名的函数
  • R4.3 全局对象的初始化不可依赖尚未初始化的其他对象
  • R4.4 不应在头文件的全局作用域中使用using directive
  • R4.5 头文件中不应定义匿名命名空间
  • R4.6 全局或命名空间作用域中不应存在即不是const也不是static的对象
  • R4.7 全局或命名空间作用域中不应存在非const对象
  • R4.8 全局对象不应同时被static和const关键字修饰
  • R4.9 匿名命名空间中不应使用静态声明
  • R4.10 头文件中不应存在由static关键字修饰的非成员对象、数组或函数
  • R4.11 命名空间作用域中禁用using namespace std之外的using directive
  • R4.12 不应在命名空间中引用自身
  • R4.13 不应定义全局inline命名空间
  • R4.14 全局对象、函数、类型以及命名空间名称不应太短

 

5. Type

  • 5.1 Class
    • R5.1.1 类的非常量数据成员均应为private
    • R5.1.2 类的非常量数据成员不应定义为protected
    • R5.1.3 类不应即有public数据成员又有private数据成员
    • R5.1.4 有虚函数的基类应具有虚析构函数
    • R5.1.5 对于菱形继承应将基类设为虚基类
    • R5.1.6 存在赋值运算符或析构函数时,不应缺少拷贝构造函数
    • R5.1.7 存在拷贝构造函数或析构函数时,不应缺少拷贝赋值运算符
    • R5.1.8 存在拷贝构造函数或赋值运算符时,不应缺少析构函数
    • R5.1.9 存在移动构造函数时,不应缺少移动赋值运算符
    • R5.1.10 存在移动赋值运算符,不应缺少移动构造函数
    • R5.1.11 可接受一个参数的构造函数需用explicit关键字限定
    • R5.1.12 重载的类型转换运算符需用explicit关键字限定
    • R5.1.13 不应过度使用explicit关键字
    • R5.1.14 带模板的赋值运算符不应覆盖拷贝或移动赋值运算符
    • R5.1.15 带模板的构造函数不应覆盖拷贝或移动构造函数
    • R5.1.16 重载的new和delete运算符应配对出现
    • R5.1.17 抽象类禁用拷贝赋值运算符
    • R5.1.18 数据成员的数量应在规定范围之内
    • R5.1.19 存在构造、析构或虚函数的类不应采用struct关键字
  • 5.2 Enum
    • R5.2.1 同类枚举值不应有重复
    • R5.2.2 各枚举值全不初始化,或者只初始化第一个,或者全部初始化,不应存在其他情况
    • R5.2.3 不应使用匿名枚举声明
    • R5.2.4 用enum class取代enum
  • 5.3 Union
    • R5.3.1 联合体内禁用非基本类型的对象
    • R5.3.2 禁用在类之外定义的联合体
    • R5.3.3 禁用联合体

 

6. Declaration

  • 6.1 Naming
    • R6.1.1 遵循合理的命名方式
    • R6.1.2 不应定义具有保留意义的名称
    • R6.1.3 局部名称不应被覆盖
    • R6.1.4 成员名称不应被覆盖
    • R6.1.5 同一命名空间内的类型名称不应与对象或函数名称相同
    • R6.1.6 不应存在拼写错误
  • 6.2 Qualifier
    • R6.2.1 const、volatile不可修饰引用
    • R6.2.2 const、volatile不应重复
    • R6.2.3 const、volatile限定类型时应出现在左侧
    • R6.2.4 const、volatile等关键字不应出现在基本类型名称的中间
    • R6.2.5 指向常量字符串的指针应有const关键字修饰
    • R6.2.6 enum的底层类型(underlying type)中不应出现const或volatile
    • R6.2.7 对常量的定义不应为引用
    • R6.2.8 慎用volatile关键字
    • R6.2.9 禁用restrict指针
  • 6.3 Specifier
    • R6.3.1 使用auto关键字时需注意代码的可读性
    • R6.3.2 不应使用已过时的关键字
    • R6.3.3 不应使用多余的inline关键字
    • R6.3.4 extern关键字不应作用于类成员的声明或定义
    • R6.3.5 所有重写的虚函数都应声明为override或final
    • R6.3.6 override和final关键字不应同时出现
    • R6.3.7 当有override或final关键字时,不应再出现virtual关键字
    • R6.3.8 不应将union设为final
    • R6.3.9 inline、virtual、static、typedef等关键字应出现在类型名的左侧
  • 6.4 Declarator
    • R6.4.1 用auto声明指针或引用时应显式标明*、&等符号
    • R6.4.2 禁用可变参数列表
    • R6.4.3 禁用柔性数组
    • R6.4.4 接口的参数或返回值不应被声明为void*
    • R6.4.5 类成员不应被声明为void*
    • R6.4.6 局部数组的长度不应过大
    • R6.4.7 不应将函数或函数指针和其他声明写在同一个语句中
    • R6.4.8 不建议将类型定义和对象声明写在一个语句中
  • 6.5 Object
    • R6.5.1 不应产生不受控制的无效临时对象
    • R6.5.2 不应出现不会被用到的局部声明
    • R6.5.3 对象初始化不可依赖自身的值
    • R6.5.4 参与数值运算的char变量需显式声明signed或unsigned
    • R6.5.5 表示二进制数据的char指针或数组应显式声明unsigned
  • 6.6 Parameter
    • R6.6.1 函数原型声明中的参数应具有合理的名称
    • R6.6.2 不建议虚函数的参数有默认值
    • R6.6.3 虚函数参数的默认值应与基类中声明的一致
    • R6.6.4 不应将数组作为函数的形式参数
    • R6.6.5 C代码中函数参数列表如果为空须声明为“(void)”
    • R6.6.6 C++代码中函数参数列表如果为空不应声明为“(void)”
    • R6.6.7 声明数组参数的大小时禁用static关键字
  • 6.7 Function
    • R6.7.1 派生类不应重新定义与基类相同的非虚函数
    • R6.7.2 拷贝赋值、移动赋值运算符应返回所属类的非const引用
    • R6.7.3 拷贝赋值运算符的参数应为同类对象的const左值引用
    • R6.7.4 移动赋值运算符的参数应为同类对象的非const右值引用
    • R6.7.5 不应重载取地址运算符
    • R6.7.6 不应重载逗号运算符
    • R6.7.7 不应重载“逻辑与”和“逻辑或”运算符
    • R6.7.8 拷贝赋值、移动赋值运算符不应为虚函数
    • R6.7.9 比较运算符不应为虚函数
    • R6.7.10 final类中不应声明虚函数
  • 6.8 Bitfield
    • R6.8.1 位域长度不应超过类型约定的大小
    • R6.8.2 有符号变量的位域长度不应为1
    • R6.8.3 不应对枚举变量声明位域
    • R6.8.4 禁用位域
  • 6.9 Complexity
    • R6.9.1 不建议采用复杂的声明
    • R6.9.2 不建议采用复杂的参数声明
    • R6.9.3 不建议在using别名中定义新的类
    • R6.9.4 在一个语句中不应声明过多对象或函数
  • 6.10 Other
    • R6.10.1 不应存在没有用到的标签
    • R6.10.2 不应存在未被使用的本地static函数
    • R6.10.3 避免使用std::auto_ptr

 

7. Exception

  • R7.1 确保异常的安全性
  • R7.2 异常类的构造函数或异常信息相关的函数不应抛出异常
  • R7.3 析构函数不可抛出异常
  • R7.4 与STL标准库相关的hash过程不应抛出异常
  • R7.5 对象的swap过程不可抛出异常
  • R7.6 移动构造函数和移动赋值运算符不可抛出异常
  • R7.7 禁用含throw关键字的异常规格说明
  • R7.8 重新抛出异常时应使用空throw表达式(throw;)
  • R7.9 不应在catch块外使用空throw表达式(throw;)
  • R7.10 不应抛出过于宽泛的异常
  • R7.11 不应抛出非异常类型的对象
  • R7.12 不应将指针作为异常抛出
  • R7.13 不应抛出NULL
  • R7.14 不应抛出nullptr
  • R7.15 禁用C++异常

 

8. Function

  • R8.1 main函数返回值的类型只应为int
  • R8.2 main函数不应被重载,也不应声明为inline、static或constexpr
  • R8.3 函数不应在头文件中实现
  • R8.4 不应定义过于复杂的内联函数
  • R8.5 函数的参数名称在声明和实现处应保持一致
  • R8.6 多态类的对象作为参数时不应采用值传递的方式
  • R8.7 不应存在未被使用的具名形式参数
  • R8.8 由const修饰的参数应为引用或指针
  • R8.9 局部变量在使用前必须初始化
  • R8.10 成员须在声明处或构造时初始化
  • R8.11 基类对象构造完毕之前不可调用成员函数
  • R8.12 在面向构造或析构函数体的catch块中不可访问非静态成员
  • R8.13 成员初始化应遵循声明的顺序
  • R8.14 在构造函数中不应调用虚函数
  • R8.15 在析构函数中不应调用虚函数
  • R8.16 拷贝构造函数应避免实现复制之外的功能
  • R8.17 赋值运算符应妥善处理参数就是自身对象时的情况
  • R8.18 不应存在得不到执行机会的代码
  • R8.19 有返回值的函数其所有分枝都应有明确的返回值
  • R8.20 不可返回局部对象的地址或引用
  • R8.21 避免无效的写入
  • R8.22 函数返回值不应为const对象
  • R8.23 返回值应与函数的返回类型相符
  • R8.24 函数返回值不应为相同的常量
  • R8.25 基本类型的返回值不应使用const修饰
  • R8.26 属性为noreturn的函数中不应出现return语句
  • R8.27 属性为noreturn的函数返回类型只应为void
  • R8.28 不应出现多余的跳转语句
  • R8.29 va_start或va_copy应配合va_end使用
  • R8.30 函数模版不应被特化
  • R8.31 函数的标签数量应在规定范围之内
  • R8.32 函数的行数应在规定范围之内
  • R8.33 lambda表达式的行数应在规定范围之内
  • R8.34 函数参数的数量应在规定范围之内
  • R8.35 禁止goto语句向平级的或更深层的其他作用域跳转
  • R8.36 禁止goto语句向前跳转
  • R8.37 禁用goto语句
  • R8.38 禁用setjmp、longjmp
  • R8.39 避免递归实现
  • R8.40 不应存在重复的函数实现

 

9. Control

  • 9.1 If
    • R9.1.1 if语句不应被分号隔断
    • R9.1.2 在if...else-if分枝中不应有重复的条件
    • R9.1.3 在if...else-if分枝中不应有被遮盖的条件
    • R9.1.4 if分枝和else分枝的代码不应完全相同
    • R9.1.5 if...else-if各分枝的代码不应完全相同
    • R9.1.6 if分枝和其隐含的else分枝的代码不应完全相同
    • R9.1.7 没有else子句的if语句与其后续代码相同是可疑的
    • R9.1.8 if分枝和else分枝的起止语句不应相同
    • R9.1.9 if语句作用域的范围不应有误
    • R9.1.10 如果if关键字前面是右大括号,if关键字应另起一行
    • R9.1.11 if语句的条件不应为赋值表达式
    • R9.1.12 if语句不应为空
    • R9.1.13 if...else-if分枝数量应在规定范围之内
    • R9.1.14 if分枝中的语句应该用大括号括起来
    • R9.1.15 所有if...else-if分枝都应以else子句结束
  • 9.2 For
    • R9.2.1 for语句不应被分号隔断
    • R9.2.2 for循环中不应存在无条件的跳转语句
    • R9.2.3 for语句作用域的范围不应有误
    • R9.2.4 如果for语句没有明显的循环变量则应改为while循环
    • R9.2.5 for循环体不应为空
    • R9.2.6 for循环变量不应为浮点型
    • R9.2.7 for循环变量不应在循环体内被改变
    • R9.2.8 嵌套的for循环不应使用相同的循环变量
    • R9.2.9 for循环体应该用大括号括起来
  • 9.3 While
    • R9.3.1 while语句不应被分号隔断
    • R9.3.2 while语句中不应存在无条件的跳转语句
    • R9.3.3 while语句的条件不应为赋值表达式
    • R9.3.4 while语句作用域的范围不应有误
    • R9.3.5 while循环体不应为空
    • R9.3.6 while循环体应该用大括号括起来
  • 9.4 Do
    • R9.4.1 注意do-while(false)中可疑的continue语句
    • R9.4.2 do-while循环体不应为空
    • R9.4.3 do-while循环体应该用大括号括起来
    • R9.4.4 不建议使用do语句
  • 9.5 Switch
    • R9.5.1 switch语句不应被分号隔断
    • R9.5.2 switch语句不应为空
    • R9.5.3 case常量的范围不可超出switch变量的范围
    • R9.5.4 switch语句中任何子句都应从属于某个case或default分枝
    • R9.5.5 每个case分枝应直接从属于switch结构
    • R9.5.6 不应存在紧邻default标签的空case标签
    • R9.5.7 不应存在内容完全相同的case分枝
    • R9.5.8 switch语句的条件变量或表达式不应为bool型
    • R9.5.9 不应使用只有default标签的switch语句
    • R9.5.10 不应使用只有一个case标签的switch语句
    • R9.5.11 switch语句分枝数量应在规定范围之内
    • R9.5.12 switch语句应配有default分枝
    • R9.5.13 每个非空的switch分枝都应该用无条件的break语句终止
    • R9.5.14 switch语句应该用大括号括起来
    • R9.5.15 switch语句不应嵌套
  • 9.6 Try
    • R9.6.1 不应存在空的try块
    • R9.6.2 catch块序列中catch-all块(ellipsis handler)应位于最后
    • R9.6.3 catch块序列中针对派生类的应排在前面,针对基类的应排在后面
    • R9.6.4 try块不应嵌套
  • 9.7 Catch
    • R9.7.1 应通过引用捕获异常
    • R9.7.2 捕获异常时不应产生对象切片问题
    • R9.7.3 不应存在空的catch块
    • R9.7.4 捕获异常后不应直接重新抛出异常,需对异常进行有效处理
    • R9.7.5 不应捕获过于宽泛的异常
    • R9.7.6 不应捕获非异常类型

 

10. Expression

  • 10.1 Logic
    • R10.1.1 不应出现不合逻辑的重复子表达式
    • R10.1.2 逻辑表达式中各子表达式不应自相矛盾
    • R10.1.3 条件表达式不应恒为真或恒为假
    • R10.1.4 不应使用多余的逻辑子表达式
    • R10.1.5 逻辑表达式及其子表达式的结果不应为常量
    • R10.1.6 短路规则下不应存在有副作用的子表达式
    • R10.1.7 逻辑表达式应尽量保持简洁明了
    • R10.1.8 可化简为逻辑表达式的三元表达式应尽量化简
  • 10.2 Arithmetic
    • R10.2.1 不应存在没有效果的表达式
    • R10.2.2 逗号表达式的子表达式应具有必要的副作用
    • R10.2.3 赋值表达式中不应存在被赋值变量的自增或自减运算
    • R10.2.4 子表达式的求值不应依赖特定的顺序
    • R10.2.5 注意运算符优先级,不可产生非预期的结果
    • R10.2.6 不在同一数组中的指针不可比较或相减
    • R10.2.7 bool型变量或表达式不应参与大小比较、位运算、自增自减等运算
    • R10.2.8 不应出现复合赋值的错误形式
    • R10.2.9 避免出现复合赋值的可疑形式
    • R10.2.10 &=、|=、-=、/=、%=左右子表达式不应相同
    • R10.2.11 不应使用NULL对非指针变量赋值或初始化
    • R10.2.12 赋值运算符与单目运算符之间应有空格,单目运算符与变量或表达式之间不应有空格
    • R10.2.13 赋值运算符左右子表达式不应重复
    • R10.2.14 除法运算符、求余运算符左右子表达不应重复
    • R10.2.15 减法运算符左右子表达式不应重复
    • R10.2.16 异或运算符左右子表达式不应重复
    • R10.2.17 负号不应作用于无符号整数
    • R10.2.18 不应重复使用一元运算符
    • R10.2.19 运算结果不应溢出
    • R10.2.20 位运算符不应作用于有符号整数
    • R10.2.21 移位数量不可超过相关类型比特位的数量
  • 10.3 Comparison
    • R10.3.1 比较运算应在正确的范围内进行
    • R10.3.2 不应使用==或!=判断浮点数是否相等
    • R10.3.3 指针不应与字符串常量直接比较
    • R10.3.4 不同类型的枚举值不应进行比较
    • R10.3.5 比较运算符左右子表达式不应重复
    • R10.3.6 比较运算不应作为另一个比较运算的直接子表达式
  • 10.4 Call
    • R10.4.1 返回值不应被忽略
    • R10.4.2 不可臆断返回值的意义
    • R10.4.3 避免对象切片
    • R10.4.4 非基本类型的对象不应传入可变参数列表
    • R10.4.5 C风格的格式化字符串与其参数的个数应严格一致
    • R10.4.6 C风格的格式化字符串与其参数的类型应严格一致
    • R10.4.7 不应显式调用析构函数
    • R10.4.8 在C++代码中禁用C风格字符串格式化方法
  • 10.5 Sizeof
    • R10.5.1 sizeof不应作用于有副作用的表达式
    • R10.5.2 sizeof的结果不应与0以及负数比较
    • R10.5.3 对数组参数不应使用sizeof
    • R10.5.4 sizeof不应作用于逻辑表达式
    • R10.5.5 被除数不应是作用于指针的sizeof表达式
    • R10.5.6 指针加减偏移量时计入sizeof是可疑的
    • R10.5.7 sizeof不应再作用于sizeof
    • R10.5.8 C++代码中sizeof不应作用于NULL
    • R10.5.9 sizeof不可作用于void
  • 10.6 Assertion
    • R10.6.1 断言中的表达式不应恒为真
    • R10.6.2 断言中的表达式不应有副作用
    • R10.6.3 断言中的表达式不应过于复杂
  • 10.7 Complexity
    • R10.7.1 运算符不应超过规定数量
  • 10.8 Other
    • R10.8.1 不应访问填充数据
    • R10.8.2 new表达式只可用于赋值或当作参数
    • R10.8.3 数组下标应为整形表达式
    • R10.8.4 禁用逗号表达式
    • R10.8.5 不应使用多余的括号

 

11. Literal

  • R11.1 注意可疑的字符常量
  • R11.2 字符常量中不可存在应转义而未转义的字符
  • R11.3 字符串常量中不可存在应转义而未转义的字符
  • R11.4 不应使用非标准转义字符
  • R11.5 不同前缀的字符串常量不可连接在一起
  • R11.6 字符串常量中不应存在拼写错误
  • R11.7 整数或浮点数常量的后缀应使用大写字母
  • R11.8 禁用8进制常量
  • R11.9 整数或浮点数常量应使用标准后缀
  • R11.10 小心遗漏逗号导致的非预期字符串连接
  • R11.11 不应存在magic number
  • R11.12 不应存在magic string
  • R11.13 不应使用多字符常量

 

12. Cast

  • R12.1 避免类型转换造成的数据丢失
  • R12.2 避免向下类型转换
  • R12.3 指针与整数不应相互转换
  • R12.4 类型转换时不应去掉const、volatile等属性
  • R12.5 不应强制转换无继承关系的类型
  • R12.6 不应强制转换非公有继承关系的类型
  • R12.7 多态类型与基本类型不应相互转换
  • R12.8 不可直接转换不同的字符串类型
  • R12.9 避免类型转换造成的指针运算错误
  • R12.10 对函数以及函数指针不应进行类型转换
  • R12.11 向下类型转换时应使用dynamic_cast
  • R12.12 对new表达式不应进行类型转换
  • R12.13 不应存在多余的类型转换
  • R12.14 可用static_cast、dynamic_cast完成的类型转换不可使用reinterpret_cast
  • R12.15 在C++代码中禁用C风格类型转换
  • R12.16 使用reinterpret_cast需有文档说明

 

13. Buffer

  • R13.1 对内存的读写应在有效的边界内进行
  • R13.2 数组下标不可越界
  • R13.3 为缓冲区分配足够的空间
  • R13.4 memset等函数不应作用于带有虚函数的对象
  • R13.5 memset等函数长度相关的参数不应有误
  • R13.6 memset等函数填充值相关的参数不应有误

 

14. Pointer

  • R14.1 避免空指针解引用
  • R14.2 注意逻辑表达式内的空指针解引用
  • R14.3 应判断malloc等内存分配函数的返回值是否为空
  • R14.4 用dynamic_cast转换指针时应判断结果是否为空
  • R14.5 不可解引用已被释放的指针
  • R14.6 不应将非零常量值赋值给指针
  • R14.7 不应使用false等布尔常量对指针赋值或初始化
  • R14.8 不应使用'\0'或L'\0'等字符常量对指针赋值或初始化
  • R14.9 不应使用常数0对指针赋值
  • R14.10 指针不应与bool型常量比较大小
  • R14.11 指针不应与char型常量比较大小
  • R14.12 不应判断指针大于、大于等于、小于、小于等于0
  • R14.13 避免无效的空指针检查
  • R14.14 不应重复检查指针是否为空
  • R14.15 不应判断this指针是否为空
  • R14.16 析构函数中不可使用delete this
  • R14.17 禁用delete this
  • R14.18 sizeof作用于指针是可疑的
  • R14.19 指针在释放后应置空

 

15. Style

  • R15.1 空格应遵循统一风格
  • R15.2 大括号应遵循统一风格
  • R15.3 NULL和nullptr不应混用
  • R15.4 在C++代码中不应使用NULL,应使用nullptr
  • R15.5 赋值表达式不应作为子表达式
  • R15.6 不应存在多余的分号

 

 

 

C/C++安全规则集合 Version

Bjarne Stroustrup: “C makes it easy to shoot yourself in the foot; C++ makes it harder, but when you do it blows your whole leg off.

  针对C、C++语言,本文收录了409种需要重点关注的问题,可为制定编程规范提供依据,也可为代码审计以及相关培训提供指导意见,适用于桌面、服务端以及嵌入式等软件系统。
  每个问题对应一条规则,每条规则可直接作为规范条款或审计检查点,本文是适用于不同应用场景的规则集合,读者可根据自身需求从中选取某个子集作为规范或审计依据,从而提高软件产品的安全性。

规则说明

规则按如下主题分为15个类别:

  1. Security:敏感数据保护、攻击防御等问题
  2. Resource:资源分配、使用与回收
  3. Precompile:预处理指令、宏、注释等问题
  4. Global:全局及命名空间作用域相关问题
  5. Type:类型相关的设计与实现
  6. Declaration:声明
  7. Exception:异常
  8. Function:函数实现
  9. Control:流程控制
  10. Expression:表达式
  11. Literal:常量
  12. Cast:类型转换
  13. Buffer:缓冲区
  14. Pointer:指针
  15. Style:样式、风格等问题

每条规则包括:

  • 编号:规则在本文中的章节编号,以“R”开头,称为Section-ID
  • 名称:用简练的短语描述违反规则的状况,以“ID_”开头,称为Fault-ID
  • 标题:规则的定义
  • 说明:规则设立的原因、示例、违反规则的后果、改进建议、参照依据、参考资料等内容

当代码出现违反规则的情况时,可分为:

  • Error:可直接导致错误的问题
  • Warning:可导致错误或存在隐患的问题
  • Suspicious:代码的可疑形式
  • Suggestion:对提高代码质量的建议

规则的说明包含:

  • 示例:规则相关的示例代码,指明符合规则(Compliant)的和违反规则(Non-compliant)的情况
  • 相关:与当前规则有相关性的规则,可作为扩展阅读的线索
  • 依据:规则依照的ISO/IEC标准,C规则以ISO/IEC 9899:2011为主,C++规则以ISO/IEC 14882:2011为主
  • 配置:某些规则的对象可由用户指定,审计工具可以此为参照实现定制化功能
  • 参考:规则参考的其他规范条款,如C++ Core Guidelines、MISRA、CWE、SEI CERT等,也可作为扩展阅读的线索

规则的相关性分为:

  • 特化:设规则A的特殊情况需要由规则B阐明,称规则B是规则A的特化
  • 泛化:与特化相反,称规则A是规则B的泛化
  • 相交:设两个规则针对不同的问题,但在内容上有一定的交集,称这两个规则相交

规则以“标准名称:版本 章节编号(段落编号)-性质”的格式引用标准,如“ISO/IEC 14882:2011 5.6(4)-undefined”,表示引用C++11标准的第5章第6节第4段说明的具有undefined性质的问题。

其中“性质”分为:

  • undefined:一般指某种错误,使程序产生undefined behavior
  • unspecified:标准不作明确规定的情况,由编译器或环境自主定义,具有随意性
  • implementation defined:由实现定义,也是由编译器或环境自主定义,与unspecified不同,要求有明确的文档支持
  • deprecated:已过时或已废弃的用法

本文以ISO/IEC 9899:2011以及ISO/IEC 14882:2011为主要依据,兼顾C18、C++17以及历史标准,没有特殊说明的规则同时适用于C语言和C++语言,只适用于某一种语言的规则会另有说明。

规则选取

本文是适用于不同应用场景的规则集合,读者可选取适合自己需求的规则。

指出某种错误的规则,如有“不可”、“不应”等字样的规则应尽量被选取,有“禁用”等字样的规则可能只适用于某一场景,可酌情选取。

如果将本文作为培训内容,为了全面理解各种场景下存在的问题,应选取全部规则。

规则列表

1. Security

  • R1.1 敏感数据不应以明文形式写入代码
  • R1.2 敏感数据不可被系统外界感知
  • R1.3 敏感数据在使用后应被有效清理
  • R1.4 公共成员或全局对象不应记录敏感数据
  • R1.5 预判由用户输入造成的不良后果
  • R1.6 避免在一个事务中通过路径多次访问同一文件
  • R1.7 访问共享数据应遵循合理的同步机制
  • R1.8 对文件设定合理的权限
  • R1.9 对用户设置合理的权限
  • R1.10 不应引用危险符号名称
  • R1.11 避免调用具有危险性的函数
  • R1.12 不应调用已过时的函数
  • R1.13 禁用不安全的字符串函数
  • R1.14 确保字符串以空字符结尾
  • R1.15 避免使用由实现定义的库函数
  • R1.16 除数不可存在值为0的可能性
  • R1.17 禁用atof、atoi、atol以及atoll等函数
  • R1.18 格式化字符串应为常量
  • R1.19 与程序实现相关的信息不可被外界感知
  • R1.20 不应硬编码IP地址
  • R1.21 避免使用errno

 

2. Resource

  • R2.1 资源管理应遵循面向对象的方法
  • R2.2 不可失去对已分配资源的控制
  • R2.3 不可失去对已分配内存的控制
  • R2.4 不可访问未初始化或已释放的资源
  • R2.5 资源不可被重复释放
  • R2.6 资源的分配与回收方法应配套使用
  • R2.7 用new分配的单个对象应该用delete释放,不应该用delete[]释放
  • R2.8 用new分配的数组应使用delete[]释放,不应使用delete释放
  • R2.9 对象被move之后的值不应再被使用
  • R2.10 对象申请的资源须在析构函数中释放
  • R2.11 如果构造函数抛出异常需确保相关资源没有泄漏
  • R2.12 C++代码中禁用malloc、free等内存分配与回收函数
  • R2.13 在一个语句中最多执行一次显式资源分配
  • R2.14 在栈上分配的空间以及非动态申请的资源不可被释放
  • R2.15 避免使用在栈上分配内存的函数
  • R2.16 避免不必要的内存分配
  • R2.17 避免动态内存分配
  • R2.18 标准FILE对象不应被复制

 

3. Precompile

  • 3.1 Include
    • R3.1.1 字母、数字、下划线以及点号之外的字符不应出现在头文件名称中
    • R3.1.2 include指令中不应使用反斜杠
    • R3.1.3 include指令中不应使用绝对路径
    • R3.1.4 禁用不合规的头文件
    • R3.1.5 在C++代码中不应引用C头文件
  • 3.2 Macro
    • R3.2.1 宏的命名应遵循合理的方式
    • R3.2.2 不可定义具有保留意义的宏名称
    • R3.2.3 不可取消定义具有保留意义的宏名称
    • R3.2.4 可作为子表达式的宏定义应该用括号括起来
    • R3.2.5 宏参数在宏定义的表达式中应该用括号括起来
    • R3.2.6 由多个语句组成的宏定义应该用do-while(0)括起来
    • R3.2.7 宏的实参个数不可小于形参个数
    • R3.2.8 宏的实参个数不可大于形参个数
    • R3.2.9 宏参数不应有副作用
    • R3.2.10 宏参数数量应在规定范围之内
    • R3.2.11 宏名称中不应存在拼写错误
    • R3.2.12 不应使用宏定义常量
    • R3.2.13 不应使用宏定义类型
    • R3.2.14 可由函数实现的功能不应使用宏实现
    • R3.2.15 在C++代码中不应使用宏offsetof
    • R3.2.16 在宏定义中由#修饰的参数后不应出现##
  • 3.3 Directive
    • R3.3.1 头文件不应缺少守卫
    • R3.3.2 不应出现非标准格式的预编译指令
    • R3.3.3 不应使用非标准预编译指令
    • R3.3.4 对编译警告的屏蔽应慎重
    • R3.3.5 代码在高级别的警告设置下编译
  • 3.4 Comment
    • R3.4.1 关注TODO、FIXME、XXX、BUG等特殊注释
    • R3.4.2 注释不可嵌套
    • R3.4.3 注释应出现在合理的位置
  • 3.5 Other
    • R3.5.1 除转义字符、宏定义之外不应使用反斜杠

 

4. Global

  • R4.1 不可修改std命名空间
  • R4.2 非全局命名空间中不应存在以main命名的函数
  • R4.3 全局对象的初始化不可依赖尚未初始化的其他对象
  • R4.4 不应在头文件的全局作用域中使用using directive
  • R4.5 头文件中不应定义匿名命名空间
  • R4.6 全局或命名空间作用域中不应存在即不是const也不是static的对象
  • R4.7 全局或命名空间作用域中不应存在非const对象
  • R4.8 全局对象不应同时被static和const关键字修饰
  • R4.9 匿名命名空间中不应使用静态声明
  • R4.10 头文件中不应存在由static关键字修饰的非成员对象、数组或函数
  • R4.11 命名空间作用域中禁用using namespace std之外的using directive
  • R4.12 不应在命名空间中引用自身
  • R4.13 不应定义全局inline命名空间
  • R4.14 全局对象、函数、类型以及命名空间名称不应太短

 

5. Type

  • 5.1 Class
    • R5.1.1 类的非常量数据成员均应为private
    • R5.1.2 类的非常量数据成员不应定义为protected
    • R5.1.3 类不应即有public数据成员又有private数据成员
    • R5.1.4 有虚函数的基类应具有虚析构函数
    • R5.1.5 对于菱形继承应将基类设为虚基类
    • R5.1.6 存在赋值运算符或析构函数时,不应缺少拷贝构造函数
    • R5.1.7 存在拷贝构造函数或析构函数时,不应缺少拷贝赋值运算符
    • R5.1.8 存在拷贝构造函数或赋值运算符时,不应缺少析构函数
    • R5.1.9 存在移动构造函数时,不应缺少移动赋值运算符
    • R5.1.10 存在移动赋值运算符,不应缺少移动构造函数
    • R5.1.11 可接受一个参数的构造函数需用explicit关键字限定
    • R5.1.12 重载的类型转换运算符需用explicit关键字限定
    • R5.1.13 不应过度使用explicit关键字
    • R5.1.14 带模板的赋值运算符不应覆盖拷贝或移动赋值运算符
    • R5.1.15 带模板的构造函数不应覆盖拷贝或移动构造函数
    • R5.1.16 重载的new和delete运算符应配对出现
    • R5.1.17 抽象类禁用拷贝赋值运算符
    • R5.1.18 数据成员的数量应在规定范围之内
    • R5.1.19 存在构造、析构或虚函数的类不应采用struct关键字
  • 5.2 Enum
    • R5.2.1 同类枚举值不应有重复
    • R5.2.2 各枚举值全不初始化,或者只初始化第一个,或者全部初始化,不应存在其他情况
    • R5.2.3 不应使用匿名枚举声明
    • R5.2.4 用enum class取代enum
  • 5.3 Union
    • R5.3.1 联合体内禁用非基本类型的对象
    • R5.3.2 禁用在类之外定义的联合体
    • R5.3.3 禁用联合体

 

6. Declaration

  • 6.1 Naming
    • R6.1.1 遵循合理的命名方式
    • R6.1.2 不应定义具有保留意义的名称
    • R6.1.3 局部名称不应被覆盖
    • R6.1.4 成员名称不应被覆盖
    • R6.1.5 同一命名空间内的类型名称不应与对象或函数名称相同
    • R6.1.6 不应存在拼写错误
  • 6.2 Qualifier
    • R6.2.1 const、volatile不可修饰引用
    • R6.2.2 const、volatile不应重复
    • R6.2.3 const、volatile限定类型时应出现在左侧
    • R6.2.4 const、volatile等关键字不应出现在基本类型名称的中间
    • R6.2.5 指向常量字符串的指针应有const关键字修饰
    • R6.2.6 enum的底层类型(underlying type)中不应出现const或volatile
    • R6.2.7 对常量的定义不应为引用
    • R6.2.8 慎用volatile关键字
    • R6.2.9 禁用restrict指针
  • 6.3 Specifier
    • R6.3.1 使用auto关键字时需注意代码的可读性
    • R6.3.2 不应使用已过时的关键字
    • R6.3.3 不应使用多余的inline关键字
    • R6.3.4 extern关键字不应作用于类成员的声明或定义
    • R6.3.5 所有重写的虚函数都应声明为override或final
    • R6.3.6 override和final关键字不应同时出现
    • R6.3.7 当有override或final关键字时,不应再出现virtual关键字
    • R6.3.8 不应将union设为final
    • R6.3.9 inline、virtual、static、typedef等关键字应出现在类型名的左侧
  • 6.4 Declarator
    • R6.4.1 用auto声明指针或引用时应显式标明*、&等符号
    • R6.4.2 禁用可变参数列表
    • R6.4.3 禁用柔性数组
    • R6.4.4 接口的参数或返回值不应被声明为void*
    • R6.4.5 类成员不应被声明为void*
    • R6.4.6 局部数组的长度不应过大
    • R6.4.7 不应将函数或函数指针和其他声明写在同一个语句中
    • R6.4.8 不建议将类型定义和对象声明写在一个语句中
  • 6.5 Object
    • R6.5.1 不应产生不受控制的无效临时对象
    • R6.5.2 不应出现不会被用到的局部声明
    • R6.5.3 对象初始化不可依赖自身的值
    • R6.5.4 参与数值运算的char变量需显式声明signed或unsigned
    • R6.5.5 表示二进制数据的char指针或数组应显式声明unsigned
  • 6.6 Parameter
    • R6.6.1 函数原型声明中的参数应具有合理的名称
    • R6.6.2 不建议虚函数的参数有默认值
    • R6.6.3 虚函数参数的默认值应与基类中声明的一致
    • R6.6.4 不应将数组作为函数的形式参数
    • R6.6.5 C代码中函数参数列表如果为空须声明为“(void)”
    • R6.6.6 C++代码中函数参数列表如果为空不应声明为“(void)”
    • R6.6.7 声明数组参数的大小时禁用static关键字
  • 6.7 Function
    • R6.7.1 派生类不应重新定义与基类相同的非虚函数
    • R6.7.2 拷贝赋值、移动赋值运算符应返回所属类的非const引用
    • R6.7.3 拷贝赋值运算符的参数应为同类对象的const左值引用
    • R6.7.4 移动赋值运算符的参数应为同类对象的非const右值引用
    • R6.7.5 不应重载取地址运算符
    • R6.7.6 不应重载逗号运算符
    • R6.7.7 不应重载“逻辑与”和“逻辑或”运算符
    • R6.7.8 拷贝赋值、移动赋值运算符不应为虚函数
    • R6.7.9 比较运算符不应为虚函数
    • R6.7.10 final类中不应声明虚函数
  • 6.8 Bitfield
    • R6.8.1 位域长度不应超过类型约定的大小
    • R6.8.2 有符号变量的位域长度不应为1
    • R6.8.3 不应对枚举变量声明位域
    • R6.8.4 禁用位域
  • 6.9 Complexity
    • R6.9.1 不建议采用复杂的声明
    • R6.9.2 不建议采用复杂的参数声明
    • R6.9.3 不建议在using别名中定义新的类
    • R6.9.4 在一个语句中不应声明过多对象或函数
  • 6.10 Other
    • R6.10.1 不应存在没有用到的标签
    • R6.10.2 不应存在未被使用的本地static函数
    • R6.10.3 避免使用std::auto_ptr

 

7. Exception

  • R7.1 确保异常的安全性
  • R7.2 异常类的构造函数或异常信息相关的函数不应抛出异常
  • R7.3 析构函数不可抛出异常
  • R7.4 与STL标准库相关的hash过程不应抛出异常
  • R7.5 对象的swap过程不可抛出异常
  • R7.6 移动构造函数和移动赋值运算符不可抛出异常
  • R7.7 禁用含throw关键字的异常规格说明
  • R7.8 重新抛出异常时应使用空throw表达式(throw;)
  • R7.9 不应在catch块外使用空throw表达式(throw;)
  • R7.10 不应抛出过于宽泛的异常
  • R7.11 不应抛出非异常类型的对象
  • R7.12 不应将指针作为异常抛出
  • R7.13 不应抛出NULL
  • R7.14 不应抛出nullptr
  • R7.15 禁用C++异常

 

8. Function

  • R8.1 main函数返回值的类型只应为int
  • R8.2 main函数不应被重载,也不应声明为inline、static或constexpr
  • R8.3 函数不应在头文件中实现
  • R8.4 不应定义过于复杂的内联函数
  • R8.5 函数的参数名称在声明和实现处应保持一致
  • R8.6 多态类的对象作为参数时不应采用值传递的方式
  • R8.7 不应存在未被使用的具名形式参数
  • R8.8 由const修饰的参数应为引用或指针
  • R8.9 局部变量在使用前必须初始化
  • R8.10 成员须在声明处或构造时初始化
  • R8.11 基类对象构造完毕之前不可调用成员函数
  • R8.12 在面向构造或析构函数体的catch块中不可访问非静态成员
  • R8.13 成员初始化应遵循声明的顺序
  • R8.14 在构造函数中不应调用虚函数
  • R8.15 在析构函数中不应调用虚函数
  • R8.16 拷贝构造函数应避免实现复制之外的功能
  • R8.17 赋值运算符应妥善处理参数就是自身对象时的情况
  • R8.18 不应存在得不到执行机会的代码
  • R8.19 有返回值的函数其所有分枝都应有明确的返回值
  • R8.20 不可返回局部对象的地址或引用
  • R8.21 避免无效的写入
  • R8.22 函数返回值不应为const对象
  • R8.23 返回值应与函数的返回类型相符
  • R8.24 函数返回值不应为相同的常量
  • R8.25 基本类型的返回值不应使用const修饰
  • R8.26 属性为noreturn的函数中不应出现return语句
  • R8.27 属性为noreturn的函数返回类型只应为void
  • R8.28 不应出现多余的跳转语句
  • R8.29 va_start或va_copy应配合va_end使用
  • R8.30 函数模版不应被特化
  • R8.31 函数的标签数量应在规定范围之内
  • R8.32 函数的行数应在规定范围之内
  • R8.33 lambda表达式的行数应在规定范围之内
  • R8.34 函数参数的数量应在规定范围之内
  • R8.35 禁止goto语句向平级的或更深层的其他作用域跳转
  • R8.36 禁止goto语句向前跳转
  • R8.37 禁用goto语句
  • R8.38 禁用setjmp、longjmp
  • R8.39 避免递归实现
  • R8.40 不应存在重复的函数实现

 

9. Control

  • 9.1 If
    • R9.1.1 if语句不应被分号隔断
    • R9.1.2 在if...else-if分枝中不应有重复的条件
    • R9.1.3 在if...else-if分枝中不应有被遮盖的条件
    • R9.1.4 if分枝和else分枝的代码不应完全相同
    • R9.1.5 if...else-if各分枝的代码不应完全相同
    • R9.1.6 if分枝和其隐含的else分枝的代码不应完全相同
    • R9.1.7 没有else子句的if语句与其后续代码相同是可疑的
    • R9.1.8 if分枝和else分枝的起止语句不应相同
    • R9.1.9 if语句作用域的范围不应有误
    • R9.1.10 如果if关键字前面是右大括号,if关键字应另起一行
    • R9.1.11 if语句的条件不应为赋值表达式
    • R9.1.12 if语句不应为空
    • R9.1.13 if...else-if分枝数量应在规定范围之内
    • R9.1.14 if分枝中的语句应该用大括号括起来
    • R9.1.15 所有if...else-if分枝都应以else子句结束
  • 9.2 For
    • R9.2.1 for语句不应被分号隔断
    • R9.2.2 for循环中不应存在无条件的跳转语句
    • R9.2.3 for语句作用域的范围不应有误
    • R9.2.4 如果for语句没有明显的循环变量则应改为while循环
    • R9.2.5 for循环体不应为空
    • R9.2.6 for循环变量不应为浮点型
    • R9.2.7 for循环变量不应在循环体内被改变
    • R9.2.8 嵌套的for循环不应使用相同的循环变量
    • R9.2.9 for循环体应该用大括号括起来
  • 9.3 While
    • R9.3.1 while语句不应被分号隔断
    • R9.3.2 while语句中不应存在无条件的跳转语句
    • R9.3.3 while语句的条件不应为赋值表达式
    • R9.3.4 while语句作用域的范围不应有误
    • R9.3.5 while循环体不应为空
    • R9.3.6 while循环体应该用大括号括起来
  • 9.4 Do
    • R9.4.1 注意do-while(false)中可疑的continue语句
    • R9.4.2 do-while循环体不应为空
    • R9.4.3 do-while循环体应该用大括号括起来
    • R9.4.4 不建议使用do语句
  • 9.5 Switch
    • R9.5.1 switch语句不应被分号隔断
    • R9.5.2 switch语句不应为空
    • R9.5.3 case常量的范围不可超出switch变量的范围
    • R9.5.4 switch语句中任何子句都应从属于某个case或default分枝
    • R9.5.5 每个case分枝应直接从属于switch结构
    • R9.5.6 不应存在紧邻default标签的空case标签
    • R9.5.7 不应存在内容完全相同的case分枝
    • R9.5.8 switch语句的条件变量或表达式不应为bool型
    • R9.5.9 不应使用只有default标签的switch语句
    • R9.5.10 不应使用只有一个case标签的switch语句
    • R9.5.11 switch语句分枝数量应在规定范围之内
    • R9.5.12 switch语句应配有default分枝
    • R9.5.13 每个非空的switch分枝都应该用无条件的break语句终止
    • R9.5.14 switch语句应该用大括号括起来
    • R9.5.15 switch语句不应嵌套
  • 9.6 Try
    • R9.6.1 不应存在空的try块
    • R9.6.2 catch块序列中catch-all块(ellipsis handler)应位于最后
    • R9.6.3 catch块序列中针对派生类的应排在前面,针对基类的应排在后面
    • R9.6.4 try块不应嵌套
  • 9.7 Catch
    • R9.7.1 应通过引用捕获异常
    • R9.7.2 捕获异常时不应产生对象切片问题
    • R9.7.3 不应存在空的catch块
    • R9.7.4 捕获异常后不应直接重新抛出异常,需对异常进行有效处理
    • R9.7.5 不应捕获过于宽泛的异常
    • R9.7.6 不应捕获非异常类型

 

10. Expression

  • 10.1 Logic
    • R10.1.1 不应出现不合逻辑的重复子表达式
    • R10.1.2 逻辑表达式中各子表达式不应自相矛盾
    • R10.1.3 条件表达式不应恒为真或恒为假
    • R10.1.4 不应使用多余的逻辑子表达式
    • R10.1.5 逻辑表达式及其子表达式的结果不应为常量
    • R10.1.6 短路规则下不应存在有副作用的子表达式
    • R10.1.7 逻辑表达式应尽量保持简洁明了
    • R10.1.8 可化简为逻辑表达式的三元表达式应尽量化简
  • 10.2 Arithmetic
    • R10.2.1 不应存在没有效果的表达式
    • R10.2.2 逗号表达式的子表达式应具有必要的副作用
    • R10.2.3 赋值表达式中不应存在被赋值变量的自增或自减运算
    • R10.2.4 子表达式的求值不应依赖特定的顺序
    • R10.2.5 注意运算符优先级,不可产生非预期的结果
    • R10.2.6 不在同一数组中的指针不可比较或相减
    • R10.2.7 bool型变量或表达式不应参与大小比较、位运算、自增自减等运算
    • R10.2.8 不应出现复合赋值的错误形式
    • R10.2.9 避免出现复合赋值的可疑形式
    • R10.2.10 &=、|=、-=、/=、%=左右子表达式不应相同
    • R10.2.11 不应使用NULL对非指针变量赋值或初始化
    • R10.2.12 赋值运算符与单目运算符之间应有空格,单目运算符与变量或表达式之间不应有空格
    • R10.2.13 赋值运算符左右子表达式不应重复
    • R10.2.14 除法运算符、求余运算符左右子表达不应重复
    • R10.2.15 减法运算符左右子表达式不应重复
    • R10.2.16 异或运算符左右子表达式不应重复
    • R10.2.17 负号不应作用于无符号整数
    • R10.2.18 不应重复使用一元运算符
    • R10.2.19 运算结果不应溢出
    • R10.2.20 位运算符不应作用于有符号整数
    • R10.2.21 移位数量不可超过相关类型比特位的数量
  • 10.3 Comparison
    • R10.3.1 比较运算应在正确的范围内进行
    • R10.3.2 不应使用==或!=判断浮点数是否相等
    • R10.3.3 指针不应与字符串常量直接比较
    • R10.3.4 不同类型的枚举值不应进行比较
    • R10.3.5 比较运算符左右子表达式不应重复
    • R10.3.6 比较运算不应作为另一个比较运算的直接子表达式
  • 10.4 Call
    • R10.4.1 返回值不应被忽略
    • R10.4.2 不可臆断返回值的意义
    • R10.4.3 避免对象切片
    • R10.4.4 非基本类型的对象不应传入可变参数列表
    • R10.4.5 C风格的格式化字符串与其参数的个数应严格一致
    • R10.4.6 C风格的格式化字符串与其参数的类型应严格一致
    • R10.4.7 不应显式调用析构函数
    • R10.4.8 在C++代码中禁用C风格字符串格式化方法
  • 10.5 Sizeof
    • R10.5.1 sizeof不应作用于有副作用的表达式
    • R10.5.2 sizeof的结果不应与0以及负数比较
    • R10.5.3 对数组参数不应使用sizeof
    • R10.5.4 sizeof不应作用于逻辑表达式
    • R10.5.5 被除数不应是作用于指针的sizeof表达式
    • R10.5.6 指针加减偏移量时计入sizeof是可疑的
    • R10.5.7 sizeof不应再作用于sizeof
    • R10.5.8 C++代码中sizeof不应作用于NULL
    • R10.5.9 sizeof不可作用于void
  • 10.6 Assertion
    • R10.6.1 断言中的表达式不应恒为真
    • R10.6.2 断言中的表达式不应有副作用
    • R10.6.3 断言中的表达式不应过于复杂
  • 10.7 Complexity
    • R10.7.1 运算符不应超过规定数量
  • 10.8 Other
    • R10.8.1 不应访问填充数据
    • R10.8.2 new表达式只可用于赋值或当作参数
    • R10.8.3 数组下标应为整形表达式
    • R10.8.4 禁用逗号表达式
    • R10.8.5 不应使用多余的括号

 

11. Literal

  • R11.1 注意可疑的字符常量
  • R11.2 字符常量中不可存在应转义而未转义的字符
  • R11.3 字符串常量中不可存在应转义而未转义的字符
  • R11.4 不应使用非标准转义字符
  • R11.5 不同前缀的字符串常量不可连接在一起
  • R11.6 字符串常量中不应存在拼写错误
  • R11.7 整数或浮点数常量的后缀应使用大写字母
  • R11.8 禁用8进制常量
  • R11.9 整数或浮点数常量应使用标准后缀
  • R11.10 小心遗漏逗号导致的非预期字符串连接
  • R11.11 不应存在magic number
  • R11.12 不应存在magic string
  • R11.13 不应使用多字符常量

 

12. Cast

  • R12.1 避免类型转换造成的数据丢失
  • R12.2 避免向下类型转换
  • R12.3 指针与整数不应相互转换
  • R12.4 类型转换时不应去掉const、volatile等属性
  • R12.5 不应强制转换无继承关系的类型
  • R12.6 不应强制转换非公有继承关系的类型
  • R12.7 多态类型与基本类型不应相互转换
  • R12.8 不可直接转换不同的字符串类型
  • R12.9 避免类型转换造成的指针运算错误
  • R12.10 对函数以及函数指针不应进行类型转换
  • R12.11 向下类型转换时应使用dynamic_cast
  • R12.12 对new表达式不应进行类型转换
  • R12.13 不应存在多余的类型转换
  • R12.14 可用static_cast、dynamic_cast完成的类型转换不可使用reinterpret_cast
  • R12.15 在C++代码中禁用C风格类型转换
  • R12.16 使用reinterpret_cast需有文档说明

 

13. Buffer

  • R13.1 对内存的读写应在有效的边界内进行
  • R13.2 数组下标不可越界
  • R13.3 为缓冲区分配足够的空间
  • R13.4 memset等函数不应作用于带有虚函数的对象
  • R13.5 memset等函数长度相关的参数不应有误
  • R13.6 memset等函数填充值相关的参数不应有误

 

14. Pointer

  • R14.1 避免空指针解引用
  • R14.2 注意逻辑表达式内的空指针解引用
  • R14.3 应判断malloc等内存分配函数的返回值是否为空
  • R14.4 用dynamic_cast转换指针时应判断结果是否为空
  • R14.5 不可解引用已被释放的指针
  • R14.6 不应将非零常量值赋值给指针
  • R14.7 不应使用false等布尔常量对指针赋值或初始化
  • R14.8 不应使用'\0'或L'\0'等字符常量对指针赋值或初始化
  • R14.9 不应使用常数0对指针赋值
  • R14.10 指针不应与bool型常量比较大小
  • R14.11 指针不应与char型常量比较大小
  • R14.12 不应判断指针大于、大于等于、小于、小于等于0
  • R14.13 避免无效的空指针检查
  • R14.14 不应重复检查指针是否为空
  • R14.15 不应判断this指针是否为空
  • R14.16 析构函数中不可使用delete this
  • R14.17 禁用delete this
  • R14.18 sizeof作用于指针是可疑的
  • R14.19 指针在释放后应置空

 

15. Style

  • R15.1 空格应遵循统一风格
  • R15.2 大括号应遵循统一风格
  • R15.3 NULL和nullptr不应混用
  • R15.4 在C++代码中不应使用NULL,应使用nullptr
  • R15.5 赋值表达式不应作为子表达式
  • R15.6 不应存在多余的分号

更多文章请关注《万象专栏》