规范1:【内存使用必须遵循谁申请谁释放原则】
规范2:【内存释放函数和该内存的申请函数必须配套使用】
规范3:【申请内存后必须要先判断内存有效性】
规范4:【内存拷贝前必须进行长度有效性判断,避免内存越界】
规范5:【禁止引用已经释放的内存空间】
规范6:【指针使用前必须进行有效性判定,避免使用空指针】
规范7:【指针内存释放后,对应指针必须置空】
规范8:【释放结构体/数组/各类数据容器指针前,必须先释放成员指针】
规范9:【严禁使用未初始化的变量作为右值】
规范10:【使用循环变量必须初始化,防止出现随机数,导致死循环】
规范11:【用宏对表达式进行定义时,最外层必须使用括号】
规范12:【当给字符串分配空间、字符串复制时,一定要给结尾标志空字符’\0’留出空间】
规范13:【字符串操作时,字符串长度申请要加1】
规范14:【字符串拷贝时,一定要注意目标字符串的长度是否足够】
规范15:【数组使用前必须进行数组下标合法性判断】
规范16:【数组分配必须考虑最大情况,避免空间使用不够】
规范17:【当声明用于分布式环境或不同CPU间通信环境的数据结构时,必须考虑机器的
字节顺序、使用的位域及字节对齐等问题】
规范18:【类型强制转换要防止越界】
规范19:【运算符使用必须加括号进行保护,禁止使用默认优先级】
规范20:【对加、减、乘等运算要进行溢出保护】
规范21:【除法运算,必须要对除零操作做有效保护】
规范22:【对函数传入参数必须进行有效性的检查】
规范23:【禁止返回函数中定义的局部指针变量】
规范24:【类中申请的成员内存,在类析构前必须释放】
规范25:【多线程编程,对于要求线程安全的变量、函数、资源,必须加锁保护,避免出现
两个任务访问异常,导致内存越界】
1.1 内存管理
规范1:【内存使用必须遵循谁申请谁释放原则】
说明:谁申请内存,理应由申请人发起释放内存的动作,依赖他人释放内存,难以得到保证,导致内存泄露的可能增加。
规范2:【内存释放函数和该内存的申请函数必须配套使用】
说明:函数中分配的内存,内存释放和申请必须要配套,注意在函数的各个返回分支都要释放,特别是异常分支处理也不要遗漏,避免内存出现泄露。
错误示例:
...
char *data = (char *)malloc(mem_size);
...
if (errno) {
return;
}
error:
if (data != NULL) {
free(data);
}
正解示例:
...
char *data = (char *)malloc(mem_size);
...
if (errno) {
goto error; >>>修正方案,出现错误时,跳转到错误处理分支释放资源
}
error:
if (data != NULL) {
free(data);
}
规范3:【申请内存后必须要先判断内存有效性】
说明内存申请时,可能出现不可预知的事情,避免对申请失败的内存进行写操作。申请内存后必须要先判断内存的有效性,再对其进行操作。
引申:申请任何资源都必须进行有效性判断后再使用,包换但不限于数据库资源池、共享内存等。
错误示例:
char *outbuf = (char *)malloc(100);
memset(outbuf, 0, 100);
......
正确示例:
char *outbuf = malloc(100);
if (NULL == inbuf)
{
error("Could not allocate outbuf bufer");
return 0;
}
memset(outbuf, 0, 100);
......
规范4:【内存拷贝前必须进行长度有效性判断,避免内存越界】
引申:避免使用魔鬼数字来进行人工计算字符长度,引起内存越界。
错误示例:
说明,paramlen是其他模块传过来的参数,是可变长的
memcpy(dst_addr, src_addr, paramlen);
>>>修正方案,先判断paramlen长度,避免超过dst_addr指向空间的长度。
正常情况下,其他模块传过来的参数是正常的,不会有问题;
异常情况下,如果其他模块传过来的长度超长,则会发生内存越界,导致系统异常。
规范5:【禁止引用已经释放的内存空间】
说明:内存释放后,如果再引用将导致不可预知的错误。
错误示例:
char* c;
std::string s="1234";
c = s.c_str();
c最后指向的内容是垃圾,因为s对象被析构,其内容被处理
正确示例:
char c[20];
std::string s="1234";
strcpy(c, s.c_str());
这样才不会出错,c_str()返回的是一个临时指针,不能对其进行操作
1.2 指针管理
规范6:【指针使用前必须进行有效性判定,避免使用空指针】
说明:操作空指针,可能导致死机问题。
规范7:【指针内存释放后,对应指针必须置空】
说明:内存释放后置NULL,避免野指针操作。
释放了内存却继续使用它,也会导致诸多问题,常见的释放之后内存还在使用有收下几种情况:
1、程序中的对象调用关系过于复杂,实在难以搞清楚某个对象究竟是否已经释放了内存,此时应该重新设计数据结构,从根本上解决对象管理的混乱局面;
2、函数的return语句写错了,返回了指向“栈内存”的“指针”或“引用”,因为该内存在函数体结束时被自动销毁;
3、使用free释放了内存后,没有将指针置为NULL,导致产生“野指针”;
4、多个指针指向同一个内存,其中一个指针释放后,其他指针成了野指针。
错误示例:
char *p = (char *) malloc(100);
strcpy(p, “hello”);
free(p); // p 所指的内存被释放,但是p 所指的地址仍然不变
…
if(p != NULL) // 没有起到防错作用
{
strcpy(p, “world”); // 出错
}
正确示例:
char *p = (char *) malloc(100);
strcpy(p, “hello”);
free(p); // p 所指的内存被释放,但是p 所指的地址仍然不变
p = NULL; >>>修正方案,将指针置为空
…
if (p != NULL) // 没有起到防错作用
{
strcpy(p, “world”); // 出错
}
规范8:【释放结构体/数组/各类数据容器指针前,必须先释放成员指针】
说明:删除结构、其他数据窗口指针时,必须从底层向上层顺序删除,确保每个元素已经释放,释放数组时,要确保数组中的每个元素指针已经释放。
错误示例:
struct BUF_S{
int len;
char *pData;
}BUF_T;
void func()
{
BUF_S *pbuf = NULL;
// 申请结构内存
// 程序处理。。。
>>>修正方案此处增加 free(pbuf->pData);
free(pbuf);
return;
}
删除了pbuf ,但pbuf->pData没有删除,必然导致内存泄露
1.3 变量、宏
规范9:【严禁使用未初始化的变量作为右值】
说明:对于局部变量的使用要保证使用前必须赋值,可在定义局部变量时就进行初始化,未经初始化的变量内容为随机值,如果做为右值会产生不可预知的错误,变量应该初始化为正解的缺省值。
引申:变量赋值错误、结构/类成员变量没有赋值直接使用。
int flag; >>>修正方案,此处进行初始化赋初值
switch (type)
{
case 1:
flag = 1;
break;
case 2:
flag = 2;
break;
default: -->进default分支将导致相应的局部变量没有赋值
/* 目前没有其他资源支持 */
break;
}
pt = flag; // 这里使用就有问题
规范10:【使用循环变量必须初始化,防止出现随机数,导致死循环】
说明:亦是未初始化,变量分配值是随机的,无法确定循环条件。另外,循环变量长度不能超过循环变量类型的最大值。
错误示例:
int count;
for (int i=0,count=0; i<100; i++)
{
// 此处对count进行赋值。。。
}
for (unsigned char j=0; j < count; j++)
{
.....
}
count的值是随机分配的,当count值大于255时,此处将出现死循环
规范11:【用宏对表达式进行定义时,最外层必须使用括号】
说明:宏只是简单的代码替换,不会像函数一样行将参数计算后,再传递,加括号避免计算顺序混乱。
错误示例:
宏定义:
#define MAX(a, b) a > b ? a : b
对宏定义使用
int i = 10;
int j = 20
int x = MAX(i, j)*10;
正解示例:
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
更复杂的宏定义应该使用do {}while(0)
#define dbg(x) do { printk("DEBUG-CMDLINE-PART: "); printk x; } while(0)
1.4 字符串
规范12:【当给字符串分配空间、字符串复制时,一定要给结尾标志空字符’\0’留出空间】
说明:strncpy等安全函数在拷贝字符串到达指定长度时,不会在目标字符串结尾添加’\0’,如果进行字符串操作,必须手工添加’\0’。
错误示例:
...
pmsg_buf = malloc(BUF_SIZE + 1);
strncpy(pmsg_buf, str_error_msg, BUF_SIZE);
...
当源字符串长度超出BUF_SIZE时,strncpy拷贝BUF_SIZE长度的字符,但目标字符串的结尾没有被赋值为’\0’,以后引用字符串pmsg_buf时可能出错。
正解示例:
...
pmsg_buf = malloc(BUF_SIZE + 1);
strncpy(pmsg_buf, str_error_msg, BUF_SIZE);
pmsg_buf [BUF_SIZE]= '\0';
...
规范13:【字符串操作时,字符串长度申请要加1】
说明:长度少1时,使用strcpy和strcat字符串拷贝时,进行字符串操作时,会出现内存越界。
错误示例:
......
char *str_src = (char *)malloc(strlen(dst));
......
strcpy(str_src, dst);
以strlen(dst)为长度申请内存,但忘记字符串结尾必须有一个’\0’,调用strcpy或strcat时拷贝了字符串本身和最后的’\0’,导致内存越界。
正确示例:
......
char *str_src = (char *)malloc(strlen(dst)+1);
......
strcpy(str_src, dst);
规范14:【字符串拷贝时,一定要注意目标字符串的长度是否足够】
说明:字符串拷贝时,必须确保目标字符串长度足够,否则容易导致内存越界。
1.5 数组、结构
规范15:【数组使用前必须进行数组下标合法性判断】
说明:数组下标要在使用前对其数值进行合法性判断,避免内存越界。
错误示例:
char slog_info[1000];
......
char stmp[10];
char *ptmp = slog_info;
.....
while(*ptmp != '-')
{
>>>修改方案,此处先对下标进行合法性判断
stmp[i++] = *ptmp++;
}
stmp[i] = '\0';
规范16:【数组分配必须考虑最大情况,避免空间使用不够】
说明:数组空间使用前要考虑使用场景,保证元素空间够用,避免内存越界。
错误示例:
char ascii[20];
int index = 0;
ascii[0] = 'a';
while (ascii[index] < 'z')
{
>>>修改方案,此处判断数组ascii的下标是否大于19,大于则跳出循环
ascii[index + 1] = ascii[index] + 1;
index++;
}
以上例子想把26个小写字母存到数组ascii中,数组ascii空间不足导致内存越界。
规范17:【当声明用于分布式环境或不同CPU间通信环境的数据结构时,必须考虑机器的
字节顺序、使用的位域及字节对齐等问题】
说明:设备使用的CPU类型复杂多样,大小端、32位/64位的处理器也都有,如果结构会在报文交互过程中使用,必须考虑字节序问题,对于跨平台的交互,数据成员发送前,都应该进行主机序到网络序的转换;接收时,也必须进行网络序到主机序的转换。
1.6 类型、表达式
规范18:【类型强制转换要防止越界】
错误示例:
typedef struct _MyTest_
{
char devname[32];
int devindex;
}MyTest_t;
char send_buf[32];
MyTest_t *my_test = NULL;
my_test = (MyTest_t *)send_buf;
snprintf(my_test->devname, 32, "/dev/sda1");
my_test->devindex = 0;
以上例子将send_buf强转成MyTest_t,然后对齐赋值,但是由于MyTest_t空间大小为36个字节,而send_buf只有32个字节,强转后必然导致越界访问。
规范19:【运算符使用必须加括号进行保护,禁止使用默认优先级】
说明:使用括号强调所使用的操作符,防止因默认的优先级与设计思想不符而导致程序出错,同时使得代码更为清晰可读。两类情况可不使用括号:
1、一元操作符,不需要使用括号
x = -n; /* 一元操作符,不需要括号 */
2、二元以上操作符,如果涉及多种操作符,则必须使用括号
x = a + b + c; /* 操作符相同,不需要括号 */
x = f(a + b, c); /* 操作符相同,不需要括号 */
if(a && b && c) /* 操作符相同,不需要括号 */
x = (a *3) + c + d; /* 操作符不相同,需要括号 */
x = (a == b) ? a : (a - b); /* 操作符不相同,需要括号 */
规范20:【对加、减、乘等运算要进行溢出保护】
for (unsigned int i = 0; i <= (len -1); i++)
使用前应检查len是否大于等于1,如果len是0,则i<=0xFFFFFFFF,程序的循环次数太大。
规范21:【除法运算,必须要对除零操作做有效保护】
错误示例:
int a, b, c;
a = 1;
b = 1;
c = a - b;
a = b / c;
上例应该对c进行非0判断,否则会出现除0操作。
1.7 函数、类
规范22:【对函数传入参数必须进行有效性的检查】
说明:对显示的输入参数进行校验,也要对容易忽略隐性的输入参数进行检测.。如果隐形入参作为数组下标,当下标超过数组元素个数,将导致内存访问越界。
错误示例:
int my_test(int index, char *buf, int len)
{
buf[index] = 100;
}
以上例子中未做参数的合法性校验。如果index大于len,则会导致非法访问。
规范23:【禁止返回函数中定义的局部指针变量】
说明:因局部指针变量指向的是函数中的栈内存,退出函数,内存将会被释放。在函数外使用该函数返回的指针将会引起不可预知的错误。
错误示例:
char *get_send_data()
{
char send_buf[32];
char *ptr = NULL;
memset(send_buf, 0, sizeof(send_buf));
ptr = send_buf;
snprintf(ptr, 32, "this is my test!\n");
return ptr;
}
以上例子ptr指向的内存空间为函数的局部变量,在函数执行完后就会被释放,如果外边使用该内存地址,会导致异常。
规范24:【类中申请的成员内存,在类析构前必须释放】
说明:类的成员内存在类被析构后,仍没有释放,将造成内存泄露,析构函数必须包括该成员的非空释放操作。
错误示例:
class CMyTest
{
public:
CMyTest()
{
test_buf_= NULL;
buf_size_ = 0;
}
~CMyTest()
{
>>>修改方案,此处增加对test_buf_的内存释放
}
int create_test_buf(unsigned int buf_size)
{
test_buf_ = new char[buf_size];
}
private:
char *test_buf_;
unsigned int buf_size_;
};
以上例子中没有在析构函数中对test_buf_做非空释放操作,很可能导致内存泄露。
规范25:【多线程编程,对于要求线程安全的变量、函数、资源,必须加锁保护,避免出现
两个任务访问异常,导致内存越界】
说明:若使用共享资源,则应通过互斥信号量(如:P、V操作)等手段对其加以保护。
错误示例:
char g_buff[32];
void ATestThread()
{
>>>修改方案,在操作g_Buffer前
snprintf(g_buff, 32, "This is A Thread!");
write(Afd, g_buff, strlen(g_Buffer));
}
void BTestThread()
{
snprintf(g_buff, 32, "This is B Thread!");
write(Bfd, g_buff, strlen(g_Buffer));
}
以上例子中,A\B两个线程都在操作g_buff,如果不对g_buff做互斥操作,会导致g_buff中的数据异常。