C避坑指南

代码段1

#include <stdint.h>

typedef struct{
    int member1;
    void (*cb_member)(void* arg);
}xxx_init_stru;

extern void xxx_init(xxx_init_stru* init);

void func1(void)
{
    xxx_init_stru* init;
    init->member1 = 0;
    init->cb_member = NULL;
    
    xxx_init(init);
}

分析

代码段2

#include <stdint.h>
#include "fsm.h"

static void event_cb(uint8_t ev, uint16_t* arg)
{
    switch(*arg){
    case 1:
        run1();
        break;
    case 2:
        run2();
    case 3:
        run3();
        break;
    default:
        break;
    }
}

void func1(void){
    extern uint16_t GetXxxInfo(void);
    uint16_t val;
    val = GetXxxInfo();
    
	FsmEventCx(0, (FsmFunc*)event_cb, &val);
}

分析

代码段3

#include <stdint.h>

typedef struct{
    int member;
    void (*cb_member)(void* arg);
}xxx_init_stru;

extern void xxx_init(xxx_init_stru* init);

void func1(void)
{
    xxx_init_stru init;
    init.member1 = 0;
    
    xxx_init(&init);
}

分析

代码段4

unsigned int xxx(const unsigned char* data, size_t len)
{
    unsigned int sum = 0;
    const unsigned char * end = data + len;
    while(data != end)
    {
        if(*data == 10){
            continue;
        }
        sum += *data++;
    }
    
    return sum;
}

分析

代码段5

#include <ctype.h>
#include <stdio.h>

int print_hex(const void* data, size_t len)
{
    char src_chr[17] = {0};
    const char *start = data;
    size_t align_16B = (len & 0x0F) ? (((len >> 4) + 1) << 4) : len;
    
    for(size_t i = 0; i < align_16B; ++i){
        if(i < len){
            src_chr[i & 0x0F] = isgraph(start[i]) ? start[i] : '.'; 
            printf(" 02X", start[i]);
        }
        else{
            src_chr[i & 0x0F] = 0;
            printf("   ");
        }
        
        if(i & 0x0F == 0x0F)
        {
            printf("\t%s\r\n", src_chr);
        }
    }
}

分析

代码段6

err_code_t err_code = ERR_CODE_SUCCESS;
#define err 		err_code

err_code_t func1(void)
{
    do{
        err_code_t err = run1();
        if(err != ERR_CODE_SUCCESS){
            break;
		}
        err = run2();
        if(err != ERR_CODE_SUCCESS){
            break;
		}
        err = run3();
        if(err != ERR_CODE_SUCCESS){
            break;
		}
        err = run4();
    }while(0);
    
    return err;
}

分析

代码段7

#define INVALID_VAL				(-1)
#define VALID_VERIFY(a)\
if((a) == INVALID_VAL)\
	return INVALID_VAL

bool get_val(int &a);

int func1(void)
{
    int val;
    if(get_val(&val))
        VALID_VERIFY(a);
    else
        return INVALID_VAL;
    run(a);
}

分析

代码段8

  • 文件1
int g_status = STATUS_IDEL;
  • 文件2
extern int g_status = STATUS_IDEL;  

分析


代码段1分析

#include <stdint.h>

typedef struct{
    int member1;
    void (*cb_member)(void* arg);
}xxx_init_stru;

extern void xxx_init(xxx_init_stru* init);

void func1(void)
{
    xxx_init_stru* init;  //指针指向的内容没有分配内存,指向位置未知,程序可能运行正常,但是可能导致其他代码出现工作异常。未注意野指针现象,异常很难排查。
    init->member1 = 0;
    init->cb_member = NULL;
    
    xxx_init(init);
}
  • 问题分析

野指针问题,使用了为初始化的指针。指针指向的内容没有分配内存,指向位置未知,程序可能运行正常,但是可能导致其他代码出现工作异常。很难排查。在使用动态内存的时候更容易出现,如多个地方使用同一块动态内存,其中一个地方释放了该内存块,但是其他使用该内存的地方并不知道,继续对该内存进行操作,而该内存区域可能又被分配给其他地方使用,从而出现很难排查的错误。

  • 问题预防

对于使用指针的地方一定要注意该指针指向的位置是否有分配内存(静态分配,动态分配)。对于使用全局相关的指针,且内存为动态分配的时候,在释放后一定要将该指针置空,且使用指针前一定要进行空指针判定。

跳回

代码段2分析

#include <stdint.h>
#include "fsm.h"

static void event_cb(uint8_t ev, uint16_t* arg)
{
    switch(*arg){
    case 1:
        run1();
        break;
    case 2:
        run2();		//没有break,执行完run2()后会立刻执行run3(),语法正确,编译器不会报错。如需某标签不需要break,应注释说明。
    case 3:
        run3();
        break;
    default:
        break;
    }
}

void func1(void){
    extern uint16_t GetXxxInfo(void);
    uint16_t val;
    val = GetXxxInfo();
    
	FsmEventCx(0, (FsmFunc*)event_cb, &val); //传递的指针是局部变量,该变量分配的是栈空间,函数退出后,可能被其他函数使用修改。
}
  • 问题分析
  1. switch的case 2没有break,执行完run2()后会立刻执行run3(),该段语句语法正确,编译器不会报错和警告。
  2. FsmEventCx传递的参数指向的变量为局部变量,局部变量执行过程中分配的是栈空间,这部分内存会在程序执行过程中由程序动态分配,函数退出后,会被回收,再分配使用。
  • 问题预防

1.对于该问题,在使用switch的时候,可以先将分支和break写完后,在补充各个分支的处理代码。比较不容易出现该错误。对于确实需要使用该特性的代码,应在代码中注释说明,否则应该视为bug。

2.对于使用指针传输局部变量等数据的时候,需要特别注意被调函数会在什么时候使用该指针传递的数据。如对于串口的发送数据函数,阻塞的接口可以使用而非阻塞的接口一般不能使用。

跳回

代码段3分析

#include <stdint.h>

typedef struct{
    int member;
    void (*cb_member)(void* arg);
}xxx_init_stru;

extern void xxx_init(xxx_init_stru* init);

void func1(void (*cb)())
{
    xxx_init_stru init;   //结构体没有初始化
    init.member1 = 0;
    
    xxx_init(&init);		//没有对cb_member变量进行初始化,其值不确定。
}
  • 问题分析

定义的局部变量没有初始化,且使用的时候也有成员没有赋值

  • 问题预防

1.对于该问题,在使用switch的时候,可以先将分支和break写完后,在补充各个分支的处理代码。比较不容易出现该错误。对于确实需要使用该特性的代码,应在代码中注释说明,否则应该视为bug。

2.对于使用指针传输局部变量等数据的时候,需要特别注意被调函数会在什么时候使用该指针传递的数据。如对于串口的发送数据函数,阻塞的接口可以使用而非阻塞的接口一般不能使用。

跳回

代码段4分析

unsigned int xxx(const unsigned char* data, size_t len)
{
    unsigned int sum = 0;
    const unsigned char * end = data + len;
    while(data != end)
    {
        if(*data == 10){
            continue;  //未执行data++,陷入死循环
        }
        sum += *data++;
    }
    

    return sum;

}
  • 问题分析

使用continue时没有对判断的条件进行变换,导致程序工作异常。

  • 问题预防

每次判断条件的改变都是一致的情况可以使用for循环进行处理。

跳回

代码段5分析

#include <ctype.h>
#include <stdio.h>

int print_hex(const void* data, size_t len)
{
    char src_chr[17] = {0};
    const char *start = data;
    size_t align_16B = (len & 0x0F) ? (((len >> 4) + 1) << 4) : len;
    
    for(size_t i = 0; i < align_16B; ++i){
        if(i < len){
            src_chr[i & 0x0F] = isgraph(start[i]) ? start[i] : '.'; 
            printf(" 02X", start[i]);
        }
        else{
            src_chr[i & 0x0F] = 0;
            printf("   ");
        }
        
        if(i & 0x0F == 0x0F)  //优先级问题, '==' 的优先级高于 '&' 
        {
            printf("\t%s\r\n", src_chr);
        }
    }
}
  • 问题分析

对优先级的判断有误,导致程序运行问题。

  • 问题预防

对于没有把握的优先级,可以使用小括号来处理,同时也能更清晰的表达表达式的含义

跳回

代码段6分析

err_code_t err_code = ERR_CODE_SUCCESS;
#define err 		err_code

err_code_t func1(void)
{
    do{
        err_code_t err = run1();
        if(err != ERR_CODE_SUCCESS){
            break;
		}
        err = run2();
        if(err != ERR_CODE_SUCCESS){
            break;
		}
        err = run3();
        if(err != ERR_CODE_SUCCESS){
            break;
		}
        err = run4();
    }while(0);
    
    return err;
}
  • 问题分析

变量(宏)重名和作用域问题,函数内部的err在do循环结束后生命周期就结束了,但因为外部有重名的宏,导致编译器未报错。

  • 问题预防

关注变量的作用域,对于宏建议全用大写,函数和变量名用小写,变量通过作用域添加标识等

跳回

代码段7分析

#define INVALID_VAL				(-1)
#define VALID_VERIFY(a)\
if((a) == INVALID_VAL)\
	return INVALID_VAL

bool get_val(int &a);

int func1(void)
{
    int val;
    if(get_val(&val))
        VALID_VERIFY(a); 
    else  // else 悬挂问题,此处else匹配的是宏展开后的if语句
        return INVALID_VAL;
    run(a);
}
  • 问题分析

else和最近可匹配的if语句匹配,上述代码段的else最近可匹配的if是宏展开后的if语句,导致逻辑错误

  • 问题预防

对于if语句,不管后续是否只有一个语句,建议都添加’{}’,划明作用域;复杂宏建议用‘do{}while(0)’包裹

跳回

代码段8分析

  • 文件1
int g_status = STATUS_IDEL;
  • 文件2
extern int g_status = STATUS_IDEL;  
  • 问题分析

外部声明全局变量不能初始化,否则将视为重新定义一个变量。编译器会产生警告,但是不会报错,

  • 问题预防

extern 全局变量放到头文件中(初始化会在实际定义的变量的文件报重复定义的错误),少用或者不用全局变量,使用静态的全局变量,并提供接口修改变量。

跳回

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值