C/C++代码规范

按照规范可能写不出简洁优雅的代码,但是一定程度上可以避免基础问题,减少沟通的成本

代码总体原则:

  1. 清晰第一。清晰性是易于维护、易于重构的程序必需具备的特征。
  2. 简洁为美。简洁就是易于理解并且易于实现。
  3. 选择合适的风格,与代码原有风格保持一致。

一、C代码规范

二、C++代码规范

可以写的东西很多

三、代码红线(常见低级问题)

3.1 直接使用容器下标读写容器的代码,并未对容器下标是否在有效范围内做判断。

典型场景,从上软获取了一个容器返回值或容器入参,使用的临时非受限的index变量去获取容器内容,在外部输入的容器不可信的情况下,可能造成越界读从而复位、闪退或未知业务异常。

3.2 禁止访问空指针

指针不判空直接使用的场景。构造单例时new存在抛异常风险,该单例对象指针存在空指针风险,可能造成闪退、复位或不可预知业务风险。

建议:

  1. 严禁对空指针进行访问,访问前需要判空。
  2. 指针作为函数参数时,请检查参数是否为空。
  3. 对于指针所代表的地址空间的任何操作,一定要保证空间的有效性。
  4. 指针变量声明必须赋予初值。变量声明赋予初值,可以避免由于编程人员的疏忽导致的变量未初始化引用。

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注入漏洞。

  • 作为输入/输出格式化字符串

    可能产生格式化字符串漏洞。

  • 作为内存复制长度

    可能造成缓冲区溢出问题。

  • 作为文件路径

    直接打开不可信路径,可能会导致目录遍历攻击,攻击者操作了无权操作的文件,使得系统被攻击者所控制。

3.10 异常分支需要记录维测日志

3.11 正确使用容器,设计合理的数据结构

results matching ""

    No results matching ""