程序该怎么写,谈谈我个人的C语言的程序编写习惯

中山野鬼 发布于 2012/03/16 23:54
阅读 5K+
收藏 24

带过些号称熟悉C语言的刚毕业的新人。发现有些通病。基本如下:

1、代码片过长。一个函数代码过长。

2、常量到处是数字,(原则上,每个常量都有明确含义,既然有含义就应该用#define符号化。

3、printf用,scanf也用。输入输出不喜欢用文件,手工敲数据或把测试数据当静态常量数组方式存放在代码里。

4、没有测试函数

5、不知道如何区分C文件。要么一个main搞定。要么刻意给我增加N多C文件。

6、不知道简单的makefile的规则和使用。

7、代码总量小于500行,改改还能应付,超过2000行,基本调整不动了。这边改了那边错,那边改正确,这边又错。

8、我能猜出(有时需要努力一下)他的代码片想干什么,但我不猜不出这种写法和目标有什么联系。

我这里说的一些方法,主要是针对程序原创的设计,例如我现在针对GIT的源码的学习和后期的改动属于另一种方法,和下面说的方法不一样。

我的基本思想是,尽可能加快反馈环和缩小反馈环的目标代码片即:规划-->设计-->验证--->修改--->再验证。

因此,原创代码的设计,我的态度,基本遵循自顶向下设计,而不是自下向上设计。

至少以下经验,实际实现过大约万行级的国际标准的算法整体重写设计。我认为至少针对万行级的代码还是可操作的。

第一步,准备环境

包括工具链的学习。哪怕你就会一个gcc 将源码直接编译连接成可执行文件的操作。

第二步,写好main 函数,就是一个hello world 。如下

#include <stdio.h>

int main(int argc ,char *argv[]){
   printf("hello world!\n");
   printf("param have %d , \n",argc);
   while (argc > 0){
      printf("%s\n",*argv++);
   }
   return 0;
}

 余下,你就开始编译,连接,并执行。这至少表示你的代码开始可以正确的run了。而且对于初学者,你直接用GCC命令方式操作。如果你觉得烦的时候,去看看makefile的资料。你就知道makefile的好处,已经可以有针对性的学习到makefile的有价值的内容了。专门为学makefile而看资料,真的会一头雾水。

第三步,增加信息断点打印代码和文件输入输出

最好是二进制方式,当然你也可以写文本方式。最简单但未必高效的方案如下,以下没有写信息输出的代码,可以参考我关于“git 源码分析,环境配置”的帖子。帖子全名忘了。呵呵。

FILE *fout = 0;
FILE *fin = 0;

void fclose_in_out(void){
    if (fout) { fclose(fout);}
    if (fin) { fclose(fout);}
}
int fopen_in_out(void){
    fout = fopen("debug.log","wt");
    fin = fopen("input.dat","rb");//二进制,也可以写成文本方式
    return !((int)fout & (int)fin)); //存在一个为0则返回1
}

int main(int argc ,char *argv[]){
    if (fopen_in_out()) {
       printf("error could not open debug.log or input.dat file!\n");
       fclose_in_out();
       return 1;
    }
    atexit(fclose_in_out()); //这两行放在顶端
    ....
}

 第四步,测试输入输出是否捣通(“捣”这个字,情色了一点,不过我是个粗人,大家谅解)

简单的将,就是将输入文件的所有内容。读出来,并写到输出文件里。最好都是二禁制或都是文本格式。然后用文件比较命令在命令行下比较。通常我自己写了个小程序,进行二进制比较,出现错误会给出出错点的位置信息并在首次出错时停住。

这样做的目的是明确了你的程序,输入有了。输出也有了。而且中间转通了。例如下面的代码

...
#define INPUT_BUF_LEN 1024
char g_inbuf[INPUT_BUF_LEN];//全局变量记得前缀一下
#define g_outbuf g_inbuf //暂时我们输入和输出BUF是同一个玩意
int main(int argc ,char *argv[]){

   ...

    while (1){
     //循环处理你的数据
       int data_len;
       if (!(data_len = get_in_data((char *)g_inbuf)){ 
         break;//输入数据结束,跳出循环,OVER。
        }
       done_data(g_inbuf,g_outbuf,data_len);
       push_out_data((char*)g_outbuf,data_len);
    }

    ...

}

为什么写while(1)方式,因为很多程序是一种服务。在内存中,通常以进程的方式存在。进程本身就存在几个状态。用while(1)的方式,可以强迫你明确退出状态和中间一些状态的变迁。

而此时,需要将上述while中的三个函数在其他C文件中写出。这时基本上就该看makefile了。因为你gcc手工玩,会比较累了。

第5步 这个阶段就很长了。逐步细化你上述三个函数。慢慢实现你的目标。

而每次细化完毕,都需要灌入数据,并对结果进行文件比较方式的判断。看期望和实际运行结果的差异。当出错时,通过检测出错数据在文件中的位置,检测属于第几次循环写出,该循环写出时,对应处理模块各个环节的中间值是否等于预期(通过信息打印方式实现)。

信息打印属于向strerr中写入信息。你需要重定向该文件,或者直接在屏幕上打印。但尽可能打到一个专门的文件中。

最后的建议。每次改动,特别是模块增加时,先用个空函数(有代码,但只是象征性的过次数据)模拟该动作,在细化内部实际操作。而且尽可能的步步为营,版本上传(通常本机我会自己做个版本服务器),上传前,一定要跑,并对输出进行比对验证。这样做的好处是不用单步跟踪,而采用正向设计分析方法来判断已有的代码改动的错误。

上述这些方法,眼尖的人会看出,有效的推进依赖大量中间测试数据的正确性。确实是这样的。大量中间测试数据其实是可以构造出来的。根据算法分析。或并行的第三方原理性代码中间截取出来的。

当不存在原理性代码时,上述做法,依赖于你写两套版本的代码。将两套不同实现方法的代码的数据进行交叉比对。这个变态的事情我做过。其理论基础是:

假设A出错的概率是1%,B出错的概率也是1%。当A,B同时有同样错误的概率是1%%。

因此理论上可以大为降低,两套版本同时出错的可能。这其实是没有办法的办法。

补充一句:在第4步时,需要对malloc和free包装一下,如下,以判断是否有漏掉free的情况发生。

int malloc_time = 0;
void *my_malloc(unsigned int size){
    char *tmp = (char*)malloc(size);
    malloc_time += (tmp != 0);
    return (void *)tmp;
}
void my_free(void **pp){
   malloc_time -= (*p != 0);
   if (*p){
      free(*p);
   }
   *p = 0;
   return ;
}
void print_malloc_time(void){//此函数在main 里面用atexit调用
    printf("malloc_time is %d\n",malloc_time);
    return ;
}

如果最后屏幕打印不是0。则表示存在遗漏 free或重复free的情况。

但这个不能保证指针越界的情况发生。指针越界的情况发生,通常是在细化代码时,通过跑测试数据来检测。没加新代码,正常,加了新代码程序异常。则可以直接针对新加代码进行检查。

以上方法只是代码设计的方法,和系统构架(系统设计分析)与测试数据构造没有关系。

测试数据构造是门学问。评判测试数据是否好还是不好,我个人的意见是,根据是否能有效保证BUG浮现来决定。

测试数据非常重要。这也是为什么我一般在测试数据,环境搭建 系统规划:代码设计 : 工程级debug 的时间比例为 2:3:5的原因。工程级debug之所以是5,不是因为代码到处都是debug。而是工程化,往往需要根据客户的接口需求调整和增加代码或模块以适应。算是对非内核代码的调整重构工作。

以下是话题补充:

@中山野鬼:对不起,有错误。网管!!!我无法编辑了。第一段代码例子中, while 循环内少了argc--; (2012/03/17 00:13)
加载中
0
puras
puras
捣为啥情色了?
0
沈括号
沈括号
一般的C语言程序多少行代码啊?
0
asdfsx
asdfsx

学习了~~~业余c选手膜拜中~~~
工作中有过两次c/c++的使用机会,都是实验性质的
受java的戕毒,代码写的都有一股浓浓的java味(一个c/c++程序,你包装那么多class、那么多get/set函数搞毛啊~~)

0
Yisen
Yisen
TDD还是入行的必备之书
0
中山野鬼
中山野鬼

引用来自“沈括号”的答案

一般的C语言程序多少行代码啊?
一般小程序不会超过1000行。正常的模块级的千行,很少超过万行的。另外,行和行不一样。文件总行数不代表有效代码行,同时书写分隔也和行数有关系。
0
时过境迁_
时过境迁_
windows下开发C和linux下开发C的优缺点各是什么?  建议在哪个环境下开发?  由于公司需要必须要windows开发,如果想在linux下开发C是用虚拟机里装个ubuntu好还是弄个双系统好?
0
中山野鬼
中山野鬼

引用来自“Carnaby_for_yz”的答案

windows下开发C和linux下开发C的优缺点各是什么?  建议在哪个环境下开发?  由于公司需要必须要windows开发,如果想在linux下开发C是用虚拟机里装个ubuntu好还是弄个双系统好?

关于C的编程,windows下不干净。但VC做原型确实快,可能是因为我从VC 5.0就开始用了,习惯导致的。新手肯定是VC方便,特别是断点跟踪和内存查看,还有变量跟踪。

但目前涉及到C语言做的工程,或者库,通常都是和linux有关的。而且很多是面向嵌入式的。不能说linux就是嵌入式的操作系统的代名词,但linux用的确实多。

最好双系统吧。我挺讨厌虚拟机的。呵呵个人感觉。所以我一直不喜欢玩JAVA。

0
Yisen
Yisen

推荐linux下开发,windows自己定义了太多东西,不喜欢

linux下用eclipse,还是很方便的

0
李渊
李渊
他们是在学校的时候没有努力,没有看《代码大全》
中山野鬼
中山野鬼
正解啊,光看书没用的。得实际开发中,遇到问题,才能有机会领会书的精髓。
大头先生
大头先生
我觉得跟实践不够多有很大的关系,实践的多了,这些都会慢慢归纳总结出来。这个时候再看《代码大全》之类的书,体会更深一些。
0
pugwoo
pugwoo
楼主有没有用什么IDE开发C?推荐一下
地鼠特工队
地鼠特工队
geany
返回顶部
顶部