C/C++编程规范

1、注意:strncpy、strncat等带n版本的字符串操作函数在源字符串长度超出n标识的长度时,会将包括’\0’结束符在内的超长字符串截断,导致’\0’结束符丢失。这时需要手动为目标字符串设置’\0’结束符。

    char dst[11];     // 【注意】最好每次定义时初始化为0: dst[11] = {0};
    char src[] = "0123456789";
    char *tmp = NULL;
    memset(dst, ‘@‘, sizeof(dst));
    memcpy(dst, src, strlen(src));
    dst[sizeof(dst) - 1] = ’\0’;    //【修改】dst以’\0’结尾

2、避免字符串/内存操作函数的源指针和目标指针指向内存重叠区

在使用像memcpy、strcpy、strncpy、sscanf()、sprintf()、snprintf()和wcstombs()这样的函数时,复制重叠对象会存在未定义的行为,这种行为可能破坏数据的完整性。

memcpy与memmove的目的都是将N个字节的源内存地址的内容拷贝到目标内存地址中。

但当源内存和目标内存存在重叠时,memcpy会出现错误,而memmove能正确地实施拷贝,但这也增加了一点点开销。

memmove的处理措施:

?当源内存的首地址等于目标内存的首地址时,不进行任何拷贝

?当源内存的首地址大于目标内存的首地址时,实行正向拷贝

?当源内存的首地址小于目标内存的首地址时,实行反向拷贝

3、使用格式化函数时推荐使用精度说明符

#define BUF_SIZE 128
void Compliant()
{
    char buffer[BUF_SIZE + 1];
    sprintf(buffer, "Usage: %.100s argument\n", argv[0]); /*【修改】字符串加上精度说明符 */
    /* ...do something... */
}
//通过精度限制从argv[0] 中只能拷贝 100 个字节。

4、确保无符号整数运算时不会出现反转

无符号数u1 u2,在计算u1+u2时,需要判断u1+u2是否大于UINT_MAX

    if((UINT_MAX - ui1) < ui2) //【修改】确保无符号整数运算时不会出现反转
    {
        return ERROR;
    }
    else
    {
        *ret = ui1+ ui2;
    }

5、确保有符号整数运算时不会出现溢出

    INT32 si1, INT32 si2;
    INT64 tmp = (INT64)si1 *(INT64)si2; /*【修改】确保有符号整数运算时不会出现溢出 */
    //++ 将32位有符号数转换成64位,并且计算完成后需要比较结果是否有符号数的范围内
    if((INT_MAX < tmp) || (INT_MIN > tmp))
    {
        return ERROR;
    }

6、确保整型转换时不会出现截断错误、符号错误

【截断错误】将一个较大整型转换为较小整型,并且该数的原值超出较小类型的表示范围,就会发生截断错误,原值的低位被保留而高位被丢弃。

【符号错误】从带符号整型转换到无符号整型会发生符号错误,符号错误并不丢失数据,但数据失去了原来的含义。带符号整型转换到无符号整型,最高位(high-order bit)会丧失其作为符号位的功能。如果该带符号整数的值非负,那么转换后值不变;如果该带符号整数的值为负,那么转换后的结果通常是一个非常大的正数。

    //++[符号错误绕过长度检查]
    int length;       //++ 声明为无符号数
    char buf[BUF_SIZE];
    length = atoi(argv[1]); //【错误】atoi返回值可能为负数
    if (length < BUF_SIZE)  // len为负数,长度检查无效
    {
        memcpy(buf, argv[2], length); /* 带符号的len被转换为size_t类型的无符号整数,负值被解释为一个极大的正整数。memcpy()调用时引发buf缓冲区溢出*/
        printf("Data copied\n");
    }

7、把整型表达式比较或赋值为一种更大类型之前必须用这种更大类型对它进行求值

UINT32 blockNum;
UINT64 alloc = (UINT64)blockNum * 16; /*【修改】确保整型表达式转换时不出现数值错误 */
//++ 先将两个32位的数之积转换为64位再赋值给64位的变量。

8、避免对有符号整数进行位操作符运算

说明:位操作符(~、>>、<<、&、^、|)应该只用于无符号整型操作数,因为有符号整数上的有些位操作的结果是由编译器所决定的,可能会出现出乎意料的行为或编译器定义的行为。

9、内存管理

申请内存后初始化(memset )

禁止内存指针移动后(非malloc分配后的起始值),通过该指针释放内存,会出现未知错误。因为malloc一块内存后,它的前一个字节存放了分配的内存大小,free时会根据该字节所代表的大小去free内存。

10、禁止调用OS命令解析器执行命令或运行程序,防止命令注入

禁止使用system()和popen()。替代方案是POSIX的exec系列函数或Win32 API CreateProcess()等与命令解释器无关的进程创建函数来替代。

错误示例:

system(sprintf("any_exe %s", input)); //【错误】参数不是硬编码,禁止使用system

这行代码是需要执行一个名为any_exe的程序,程序参数来自用户的输入input。这种情况下,恶意用户输入参数:

happy; useradd attacker

最终shell将字符串”any_exe happy; useradd attacker”解释为两条独立的命令连续执行:

any_exe happy

useradd attacker

这样攻击者通过注入了一条命令”useradd attacker”创建了一个新用户。这明显不是程序所希望的。

改用:

if (execve("/usr/bin/any_exe", args, envs) == -1) /*【修改】使用execve代替system */

11、禁止使用std::ostrstream,推荐使用std::ostringstream

说明: std::ostrstream的使用上需要特别注意几点:

(1)str() 会调用成员函数freeze(),它会冻结字符序列,当缓冲区不够大以至于需要分配新缓冲区时,这么做可以避免事情变得复杂。

(2)str()不会附加字符串终止符号(’\0’)。

(3)data()返回所有字符串,没有附带’\0’结尾字符(目前有些编译器自动调用c_str方法了)。

上面如果不注意,就可能会导致内存访问越界、缓冲区溢出等问题,所以建议不要使用ostrstream。[C++03]标准将std::strstream标明为deprecated,替代方案是std::stringstream。ostringstream没有上述问题。

错误示例:下列代码使用了std::ostrstream,可能会导致内存访问越界等问题。

void NoCompliant()

{

std::ostrstream mystr; //【错误】不要使用std::ostrstream

mystr << “hello world”;

// ostream.str方法返回的指针,没有空结束符,容易造成问题

char *p = mystr.str();

std::cout << mystr.str() << std::endl;

}

12、C++中,必须使用C++标准库替代C的字符串操作函数

C标准的系列字符串处理函数strcpy/strcat/sprintf/scanf/gets,不检查目标缓冲区的大小,容易引入缓冲区溢出的安全漏洞。

C++标准库提供了字符串类抽象的一个公共实现std::string,支持字符串的常规操作

13、必须使用int类型来接收字符输入/输出函数的返回值,不要使用char型

说明:字符输入/输出函数fgetc()、getc()和getchar()都从一个流读取一个字符,并把它以int值的形式返回。如果这个流到达了文件尾或者发生读取错误,函数返回EOF。fputc()、putc()、putchar()和ungetc()也返回一个字符或EOF。

如果这些I/O函数的返回值需要与EOF进行比较,不要将返回值转换为char类型

因为char是有符号8位的值,int是32位的值。如果getchar()返回的字符的ASCII值为0xFF,转换为char类型后将被解释为EOF。0xFF这个值被有符号扩展后是0xFFFFFFFF,刚好等于EOF的值。

注意:对于sizeof(int) == sizeof(char)的平台,用int接收返回值也可能无法与EOF区分,这时要用feof()和ferror()检测文件尾和文件错误。

14、文件路径验证前,必须对其进行标准化

说明:当文件路径来自非信任域时,需要先将文件路径规范化再做校验。路径在验证时会有很多干扰因素,如相对路径与绝对路径,如文件的符号链接、硬链接、快捷路径、别名等。

所以在验证路径时需要对路径进行标准化,使得路径表达唯一化、无歧义。

如果没有作标准化处理,攻击者就有机会:

推荐做法:

Linux下对文件进行标准化,可以防止黑客通过构造指向系统关键文件的链接文件。realpath() 函数返回绝对路径,删除了所有符号链接:

void  Compliant(char *lpInputPath)
{
    char realpath[MAX_PATH];
    if ( realpath(inputPath, realpath) == NULL)
        /* handle error */;
    /*... do something ...*/
}

Windows下可以使用PathCanonicalize函数对文件路径进行标准化:

void  Compliant(char *lpInputPath)
{
    char realpath[MAX_PATH];
    char *lpRealPath = realpath;
    if ( PathCanonicalize(lpRealPath,lpInputPath) == NULL)
        /* handle error */;
    /*... do something ...*/
}

15、访问文件时尽量使用文件描述符代替文件名作为输入,以避免竞争条件问题

说明:该建议应用场景如下,当对文件的元信息进行操作时(比如修改它的所有者、对文件进行统计,或者修改它的权限位),首先要打开该文件,然后对打开的文件进行操作。只要有可能,应尽量避免使用获取文件名的操作,而是使用获取文件描述符的操作。这样做将避免文件在程序运行时被替换(一种可能的竞争条件)。

例如,当access()和open()两者都利用一个字符串参数而不是一个文件句柄来进行相关操作时,攻击者就可以通过在access()和open()之间的间隙替换掉原来的文件,如下所示:

错误示例:下列代码使用access()函数,可能引发竞争条件问题。

void  Noncompliant(char * file)
{
    if(!access(file, W_OK))     //【不推荐】不要使用函数access(),易引发条件竞争
    {
        f = fopen(file, "w+");
        /*...*/
        /* close f after operate(f)*/
    }
    else
    {
        fprintf(stderr, "Unable to open file %s.\n", file);
    }
}

16、正确处理容器的erase()方法与迭代子的关系

说明:调用容器的erase(iter)方法后,迭代子指向的对象被析构,迭代子已经失效,如果再对迭代子执行递增递减或者引用操作会导致程序崩溃。

//++ 错误用法:
    m_mapID2NE.erase(iter);
    iter++;  //【错误】erase后,iter指向的对象可能已失效
//++ 正确用法:
    m_mapID2NE.erase(iter++); //【修改】将迭代子后置递增作为erase参数   

也可以使用earse方法的返回值来保存迭代子,因为返回的是被删除元素迭代子指向的下一个元素位置:iter = erase(iter)

注意这种用法可以用于list和vector的erase(),但不适用于map。因为std::map::erase()的返回值在不同STL实现版本是有差异的,有的有返回值,有的没有返回值,所以对map只能使用推荐做法。

时间: 2024-10-04 02:29:40

C/C++编程规范的相关文章

华为C语言编程规范

DKBA华为技术有限公司内部技术规范DKBA 2826-2011.5C语言编程规范2011年5月9日发布 2011年5月9日实施华为技术有限公司Huawei Technologies Co., Ltd.版权所有 侵权必究All rights reserved密级:confidentiality levelDKBA 2826-2011.52011-06-02 华为机密,未经许可不得扩散 Huawei Confidential 第2页,共61页Page 2 , Total61修订声明Revision

Windows客户端C/C++编程规范“建议”——函数

1 函数 1.1 代码行数控制在80行及以内 等级:[要求] 说明:每个函数的代码行数控制应该控制在80行以内.如果超过这个限制函数内部逻辑一般可以拆分.如果试图超过这个标准,请列出理由.但理由不包含如下: 无法拆分. 流程内部逻辑复杂,无需拆分,即使拆分了,拆分的函数也不会被其他地方用到.(解释:拆分可以减少代码行数,提炼后的函数可以方便读者快速理解函数逻辑并定位问题.) 1.2 代码列数控制在100字符及以内 等级:[要求] 说明:每行代码不可以超过100字符.如果超过这个字符数,代码的美观

Windows客户端C/C++编程规范“建议”——指针

2 指针 2.1 尽量使用智能指针 等级:[推荐] 说明:正确使用智能指针可以省去指针管理的工作. 2.2 类成员变量指针释放后一定要置空 等级:[必须] 说明:如果类成员变量指针在释放后没有置空,将出现如下问题: a) 无法判断指针是否已经是野指针 b) Dump分析很难发现是野指针函数调用导致崩溃 2.3 正确使用delete和delete[] 等级:[必须] 说明:delete[]用于释放动态分配的数组,而delete用于释放对象.两者不可以混用. 2.4 使用指针前要判空 等级:[必须]

Windows客户端C/C++编程规范“建议”——函数调用

3 函数调用 3.1 谨慎使用递归方法 等级:[推荐] 说明:递归方式控制不当,可能会导致栈空间不够而崩溃.一般的递归都可以使用循环代替. 3.2 不要使用using namespace 等级:[必须] 说明:这是曾经教科书上的一种写法,但是该方法存在严重的缺陷.因为如果多个不同的namespace里定义了相同名字的变量或者函数.将导致无法预知和理解编译器最终使用的是哪个命名空间中的数据. 例子: //file1 namespace Space1{ int g_Private = 0; }; /

[转载]通达信插件选股(基于通达信插件编程规范的简单分析)

[转载]通达信插件选股(基于通达信插件编程规范的简单分析) 原文地址:通达信插件选股(基于通达信插件编程规范的简单分析)作者:哈尔滨猫猫 首先声明,鄙人是编程人员,不是股民.对选股概念了解甚少.本文仅作编程人员学习借鉴之用.不对选股理论进行探讨和解释. 以前有客户找我做过通达信插件选股的小任务,当时第一次接触面向接口(此类“接口”)编程,也是第一次接触到股票里相关的概念.最近由于接手一个任务,与上次开发相类似,故旧事重提,仔细研究一番.将个人学习研究所得知识与大家分享.在网上搜相关资料,可用的.

最佳11个PHP编程规范

个人原创网址:http://www.phpthinking.com/archives/375 从设计之初,PHP被广泛用于开发基于Web的应用程序. 由于PHP是一种脚本语言,开发的时候必须遵守一些规范. 本文将讨论常用的良好的代码习惯,或者称为代码规范,在PHP领域. 1,错误报告开启 错误报告是在PHP中一个非常有用的功能,应同时在开发阶段启用. 这可以帮助我们确定我们的代码中的问题. 最常用的功能是"E_ALL",这有助于我们发现所有的警告和严重错误. 必须指出的是,我们把我们的

对Google C++编程规范的理解和实践

1. #define保护 所有头文件都应该使用#define 防止头文件被多重包含(multiple  inclusion),命名格式为: <PROJECT>_<PATH>_<FILE>_H_ 为保证唯一性,头文件的命名应基于其所在项目源代码树的全路径.例如,项目foo 中的头文件 foo/src/bar/baz.h按如下方式保护: #ifndef FOO_BAR_BAZ_H_ #define FOO_BAR_BAZ_H_ ... #endif // FOO_BAR_B

多线程编程规范

规则1    指定线程名  用于查看线程信息 规则2    使用Thread对象的setUncaughtExceptionHandler方法注册Runtime异常的处理者(v1.5+) 说明:Java多线程程序中,所有线程都不允许抛出未捕获的checked exception,也就是说各个线程需要自己把自己的checked exception处理掉.但是无法避免的是unchecked exception,也就是RuntimeException,当抛出异常时子线程会结束,但主线程不会知道,因为主线

Windows客户端C/C++编程规范“建议”——前言

前言 工作中接触了很多编程规范.其中最有意思的是,公司最近发布了一版C/C++编程规范,然后我看到该规范的最后一段时,有这么一句:"该规范不适用于Windows平台开发".看来这份规范是由做其他平台开发的同学制定的.那么做Windows开发的人都去哪儿了?后来由于工作需要,项目组需要我制定一份编程规范.这也是我这系列博客的由来.(转载请指明出于breaksoftware的csdn博客) 说到"规范"",可能没多少人喜欢这样的东西.相信很多工程师和我一样,都

junit编程规范

Junit使用的一下编程规范 测试方法上必须使用@Test进行修饰 测试方法必须使用public void 进行修饰,不能带任何的参数 新建一个源代码目录 测试类的包应该和被测试类保持一致 测试单元中的每个方法必须可以独立测试,测试方法间不能有任何的依赖 测试类使用Test作为类名的后缀(非必须) 测试方法使用test作为方法名的前缀(非必须)