编程规范小节

 

规范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’,调用strcpystrcat时拷贝了字符串本身和最后的’\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,如果len0,则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:【多线程编程,对于要求线程安全的变量、函数、资源,必须加锁保护,避免出现

          两个任务访问异常,导致内存越界】

说明:若使用共享资源,则应通过互斥信号量(如:PV操作)等手段对其加以保护。

 

错误示例:

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中的数据异常。


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值