【C】C 语言 code review



不合格程序

  • 错误的浮点数比较用法。
  • 指针退化仍使用 sizeof。
  • 数组越界操作。
  • == 写成 =!= 写成 =
  • 宏函数没有足够的括号来预防边界(表达式参数)。

小屁孩程序(需要为其擦屁股,不可用代码)

  • 声明变量没有赋初值。
  • 函数参数使用前没有判断特殊情况。
  • 编写函数在需要返回不同的错误情况时没有返回错误(或者全部使用 -1 表示)。
  • 函数调用没有错误返回的判断。
  • 理解不完全的错误返回判断(想当然地自己定义错误消息) - 不懂得使用 perror
  • 重复使用的字符串数组没有 memset
  • 文件描述符泄漏。
  • 使用了内含 malloc(calloc) 的 API,没有调用相应 API 释放。
  • 循环语句边界条件错误。
  • switch-case 没有 break;确定不用 break 的位置需加 /* FALL THROUGH */ 注释。
  • fclose(野指针);fclose(NULL);free(野指针);free(NULL); 发生的情况没有考虑(释放资源后没有对变量“重置”)。
  • 想当然地使用自我命名地缩写 - 无通用缩写单词使用全单词,或者保留发音字符后长度需大于原本 50%。
  • 不适宜的代码风格。
  • 有限定范围值的参数,没有对其做判断。

代码风格,错误预防

  • 在 if 语句成立情况下必定会执行 break, continue, exit, return, goto 的代码中勿用 if ... else ... (无需 else)。
  • 变量在使用的位置声明与赋值,勿全部集中在函数起始部分。
  • 长上下文变量命名使用有含义的名称 - 可将可用于该变量注释内容用下划线连接后作为变量名。
  • 函数名尽可能提供足够多的信息。
  • 不应该惧怕编写函数,而在函数内直接添加 bug fix 代码,直接添加 feature 代码。
  • 有必要就、尽可能地使用 enum 定义函数返回。
  • 尽可能做显式条件判断(不要使用 if (!<condition>) 这种写法)。
  • 多使用 struct 组织变量,为 struct 添加 void (*to_string)(struct name_s *me, char dst[]) 函数,尽可能为 struct 编写构造函数。
  • 写足够多的代码以形成确定的个人代码风格 - 公司内尽最大可能使用约定风格。
  • 必要的注释。
  • 不要滥用短路逻辑;
    • 不要在判断条件中做 fp = fopen(file, "r") 打开文件并做相关判断。
    • 不要在判断条件中做 malloc 并做相关判断。
  • 尽可能使用 const, static, extern 限定条件。
  • 弱类型语言 - 不要隐式变量类型转换;尽可能显式类型转换,对于无法做到显式的位置要做注释。


bug 实例收集 - N/A

for 循环 - N/A

声明变量没有赋初值

声明变量没有赋初值有多糟糕?下面是一个典型的代码:

#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

typedef enum {FALSE=0, TRUE} Boolean;

void print_date();
void get_date(char *);

Boolean chr2bool(char c) {
    if ((c - '0') == 0)
	    return FALSE;
    else if ((c - '0') == 1)
	    return TRUE;
    else {
	    printf("ValueError: can't convert '%c' to Boolean", c);
	    exit(1);
    }
}

int main()
{
    while (TRUE) {
        usleep(500000);
        print_date();
    }
    return 0;
}


void print_date() {
    char data[256];
    get_date(data);
    printf("data: %s\n", data);
    return;
}

void get_date(char *data) {
    Boolean flag = FALSE;
    char enter = '\0';

    printf("print date?(0 or 1, 'q' to quit)>");
    scanf("%c", &enter);
    enter == 'q' ? exit(0) : (flag = chr2bool(enter));

    if (flag == FALSE)  // simulate call ioctl, but no clients -- not write :data:.
        return;

    FILE *fp = NULL;

    fp = popen("date +\"%Y-%m-%dT%H:%M:%S:%N\"", "r");
    if (fp == NULL) {
        perror("popen: ");
        exit(errno);
    }

    char tmp[256] = {'\0'};
    fgets(tmp, 256, fp);
    memcpy(data, tmp, sizeof(char) * 256);

    pclose(fp);
    return;
}

保存为 memset.c 文件,运行方式如:

$ gcc memset.c && echo "1110001000101000q" | ./a.out
print date?(0 or 1, 'q' to quit)>data: 2019-09-12T16:12:04:290045662
print date?(0 or 1, 'q' to quit)>data: 2019-09-12T16:12:04:793379343
print date?(0 or 1, 'q' to quit)>data: 2019-09-12T16:12:05:297036989
print date?(0 or 1, 'q' to quit)>data: 2019-09-12T16:12:05:297036989
print date?(0 or 1, 'q' to quit)>data: 2019-09-12T16:12:05:297036989
print date?(0 or 1, 'q' to quit)>data: 2019-09-12T16:12:05:297036989
print date?(0 or 1, 'q' to quit)>data: 2019-09-12T16:12:07:301353222
print date?(0 or 1, 'q' to quit)>data: 2019-09-12T16:12:07:301353222
print date?(0 or 1, 'q' to quit)>data: 2019-09-12T16:12:07:301353222
print date?(0 or 1, 'q' to quit)>data: 2019-09-12T16:12:07:301353222
print date?(0 or 1, 'q' to quit)>data: 2019-09-12T16:12:09:305473044
print date?(0 or 1, 'q' to quit)>data: 2019-09-12T16:12:09:305473044
print date?(0 or 1, 'q' to quit)>data: 2019-09-12T16:12:10:309085615
print date?(0 or 1, 'q' to quit)>data: 2019-09-12T16:12:10:309085615
print date?(0 or 1, 'q' to quit)>data: 2019-09-12T16:12:10:309085615
print date?(0 or 1, 'q' to quit)>data: 2019-09-12T16:12:10:309085615
print date?(0 or 1, 'q' to quit)>
$

如果你读懂了上面的代码,就会发现代码中的 data 变量在通过 get_date(data) 的时候,如果输入是 0,那么 get_date 这个函数不会去修改 data 变量的内容。
我们知道没有初始化的变量是随机值,内存中是什么样它就是什么样。

甚至某种程度上可以不正确地认为它是空值(0)。

但是在上面这个例子中完全行不通,因为程序重复调用 print_date 函数(而不是执行一个程序调用一次 print_date 函数),所以看上去在 print_date 函数内部,data 每次都有“重新”声明,定义。但是实际上在程序运行的时候,它会重复地使用同一块内存地址,使用地还是上一次调用 print_date 时留下来的值(是的,在特定情况下,函数内没有附初值的变量行为上等同于加上了 static 关键字)。
具体在实际软件中的表现这里就不展开了,上面运行程序的输出,那样的结果就已经足够骇人听闻了!因为那明显不是正确的结果,这种情况一是不容易发生,它一般是在特定条件下发生,但是越是这种在特定条件下发生的 bug,越是令人头疼;二是在代码很大,嵌套很深的情况下,可能不是那么容易联想到原因是这样子的,而找出问题来就很麻烦。所以变量要赋初始值这一点一定要养成一个习惯!

变量没有赋初值的问题不仅仅是上面的那样情况,很有其它的表现,比如声明了指针,但是偏偏在某些特殊情况下没有用到这个指针变量,那么比如:

  • 文件指针:
    FILE *fp;
    ...codes...
    if (fp != NULL)
        fclose(fp);
    
  • malloc 内存指针:
    void *buf;
    ...codes...
    if (some-condition) {
        buf = malloc(SIZE);
        ...codes...
    }
    if (buf != NULL)
        free(buf);
    

文件指针如果是野指针(声明之后没有赋值,马上就是野指针),调用 fclose 关闭这个野指针,马上进程就会异常退出!
调用 free 野指针也一样。所以声明变量马上赋初值百利无一害!



Reference

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值