C/C++代码规范
按照规范可能写不出简洁优雅的代码,但是一定程度上可以避免基础问题,减少沟通的成本
代码总体原则:
- 清晰第一。清晰性是易于维护、易于重构的程序必需具备的特征。
- 简洁为美。简洁就是易于理解并且易于实现。
- 选择合适的风格,与代码原有风格保持一致。
一、C代码规范
二、C++代码规范
可以写的东西很多
三、代码红线(常见低级问题)
3.1 直接使用容器下标读写容器的代码,并未对容器下标是否在有效范围内做判断。
典型场景,从上软获取了一个容器返回值或容器入参,使用的临时非受限的index变量去获取容器内容,在外部输入的容器不可信的情况下,可能造成越界读从而复位、闪退或未知业务异常。
3.2 禁止访问空指针
指针不判空直接使用的场景。构造单例时new存在抛异常风险,该单例对象指针存在空指针风险,可能造成闪退、复位或不可预知业务风险。
建议:
- 严禁对空指针进行访问,访问前需要判空。
- 指针作为函数参数时,请检查参数是否为空。
- 对于指针所代表的地址空间的任何操作,一定要保证空间的有效性。
- 指针变量声明必须赋予初值。变量声明赋予初值,可以避免由于编程人员的疏忽导致的变量未初始化引用。
3.3 必须检查函数返回值
函数可以通过返回值、传出参数、内部状态、线程局部状态(如 errno 、 dlerror() )等方式报告函数执行中发生的错误。与异常不同,用这些方式报告的错误,不会立即导致函数停止执行。因此,调用者应该在函数调用之后,应及时检查用这些方法报告的错误,并做好异常分支防护。
3.4 整数之间运算时必须严格检查,确保不会出现溢出、反转、除0
在计算机中,整数存储的长度是固定的(例如32位或64位),当整数之间进行运算时,可能会超过这个最大固定长度,导致整数溢出或反转,使得实际计算结果与预期结果不符。
// 错误示例
size_t a = ReadSize();
size_t b = 1000 / a; //a可能是0
size_t c = 1000 % a; //a可能是0
// 推荐做法
size_t a = ReadSize();
if (a == 0) {
//error
...
}
size_t b = 1000 / a; //a不可能是0
size_t c = 1000 % a; //a不可能是0
3.5 不要期望浮点运算得到精确的值
实际编程中,要结合场景需求,尤其是对精度的要求,合理选择浮点数操作。例如:对于浮点值比较,如果对比较精度有要求,通常不建议直接用!=或==比较,而是要考虑epsilon。epsilon定义为浮点数能表示的比1.0大的最小值与1.0的差值,即最小精度,在C++中,则可以使用numeric_limits模板类的epsilon方法获得:
// 错误做法:
void Compare(double f)
{
if (f == 3.14) { // 不符合
...
}
}
// 推荐做法:
bool IsEqual(double a, double b)
{
return (std::fabs(a - b) <= std::numeric_limits<double>::epsilon());
}
void Compare(double f)
{
if (IsEqual(f, 3.14)) { // 符合
...
}
}
3.6 禁止使用魔鬼数字
3.7 对于输入参数,拷贝代价小的类型传值,拷贝代价大的类型传const引用
如果函数只是要使用输入参数,并不修改它,那么对于拷贝代价小的类型应当传值,对拷贝代价大的类型传 const 引用。这两种做法都允许调用者传入右值作为参数。例如:char、int 、long 、double等类型可以被认为是拷贝代价小的类型,如果传入引用,增加了间接访问的开销,其性能会比传值更差。
3.8 可能执行失败的API必须提供结果返回值
3.9 对所有外部数据进行合法性校验
来自程序外部的数据通常被认为是不可信的,在使用这些数据之前,需要进行合法性校验。如果不对这些外部数据进行校验,将可能导致不可预期的安全风险。
外部数据的来源包括但不限于:网络、用户输入、命令行、文件(包括程序的配置文件)、环境变量、用户态数据(对于内核程序)、进程间通信(包括管道、消息、共享内存、socket、RPC等,特别需要注意的是设备内部不同单板间通讯也属于进程间通信)、API参数。
对来自程序外部的数据要校验处理后才能使用。典型场景包括:
-
作为容器索引或迭代器
将不可信的数据作为容器索引或迭代器,可能导致超出容器上限,从而造成非法内存访问。
-
作为内存偏移地址
将不可信数据作为指针偏移访问内存,可能造成非法内存访问,并可以造成进一步的危害,如任意地址读写。
-
作为内存分配的尺寸参数
使用0长度分配内存可能造成非法内存访问;未限制分配内存大小会造成过度资源消耗。
-
作为循环条件
将不可信数据作为循环限定条件,可能会引发缓冲区溢出、内存越界读写、死循环等问题。
-
作为除数
可能产生除零错误(被零除)。
-
作为命令行参数
可能产生命令注入漏洞。
-
作为数据库查询语句的参数
可能产生SQL注入漏洞。
-
作为输入/输出格式化字符串
可能产生格式化字符串漏洞。
-
作为内存复制长度
可能造成缓冲区溢出问题。
-
作为文件路径
直接打开不可信路径,可能会导致目录遍历攻击,攻击者操作了无权操作的文件,使得系统被攻击者所控制。