守望者--AIR技术交流

 找回密码
 立即注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

搜索
热搜: ANE FlasCC 炼金术
查看: 2680|回复: 0
打印 上一主题 下一主题

[C语言] 快速学习C语言二: 编译自动化, 静态分析, 单元测试,coredum...

[复制链接]
  • TA的每日心情
    擦汗
    2018-4-10 15:18
  • 签到天数: 447 天

    [LV.9]以坛为家II

    1742

    主题

    2094

    帖子

    13万

    积分

    超级版主

    Rank: 18Rank: 18Rank: 18Rank: 18Rank: 18

    威望
    562
    贡献
    29
    金币
    52696
    钢镚
    1422

    开源英雄守望者

    跳转到指定楼层
    楼主
    发表于 2015-1-3 13:49:13 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
    上次的Hello world算是入门了,现在学习一些相关工具的使用
    编译自动化
    写好程序,首先要编译,就用gcc就好了,基本用法如下
    1. gcc helloworld.c -o helloworld.o
    复制代码
    helloworld.c是源码,helloworld.o是编译后的可执行文件,运行的话就用 ./helloworld.o就可以了。
    但是如果代码写的多了,每次改动完都手动用gcc编译太麻烦了,所以要用Makefile来 自动化这项工作,在当前目录下创建Makefile文件,大概如下
    1. helloworld.o: helloworld.c
    2.     gcc helloworld.c -o helloworld.o

    3. .PHONY: lint
    4. lint:
    5.     splint helloworld.c -temptrans -mustfreefresh -usedef

    6. .PHONY: run
    7. run:
    8.     ./helloworld.o

    9. .PHONY: clean
    10. clean:
    11.     rm *.o
    复制代码
    缩进为0每一行表示一个任务,冒号左边的是目标文件名,冒号后面是生成该目标的依赖 文件,多个的话用逗号隔开,如果依赖文件没有更改,则不会执行该任务。
    缩进为1的行表示任务具体执行的shell语句了,.PHONY修饰的目标表示不管依赖文件 有没有更改,都执行该任务。
    执行对应的任务的话,就是在终端上输入make 目标名,如make lint表示源码检查, make clean表示清理文件,如果只输入make,则执行第一个目标,对于上面的文件就 是生成helloworld.o了。
    现在修改完源码,值需要输入一个make回车就行了,Makefile很强大,可以做很多自动化 的任务,甚至测试,部署,生成文档等都可以用Makefile来自动化,有点像前端的 Grunt和Java里的ant,这样就比较好理解了。
    静态检查
    静态检查可以帮你提前找出不少潜在问题来,经典的静态检查工具就是lint,具体到 Linux上就是splint了,可以用yum来安装上。
    具体使用的话就是splint helloworld.c就行了,它会给出检查出来的警告和错误,还 提供了行号,让你能很快速的修复。
    值得注意的是该工具不支持c99语法,所以写代码时需要注意一些地方,比如函数里声明 变量要放在函数的开始,不能就近声明,否则splint会报parse error。
    静态检查工具最好不要忽略warning,但是有一些警告莫名其妙,我看不懂,所以还是 忽略了一些,在使用中我加上了-temptrans -mustfreefresh -usedef这几个参数。
    单元测试
    安装CUnit
    1. wget http://sourceforge.net/projects/cunit/files/latest/download
    2. tar xf CUnit-2.1-3.tar.bz2
    3. cd CUnit-2.1-3
    4. ./bootstrap
    5. ./configure
    6. make
    7. make install
    复制代码
    了解下单元测试的概念: 一次测试(registry)可以分成多个suit,一个suit里可以有多个 test case, 每个suit有个setup和teardown函数,分别在执行suit之前或之后调用。
    下面的代码是一个单元测试的架子,这里测试的是库函数strlen,这里面只有一个suit, 就是testSuite1,testSuit1里里有一特test case,就是testcase,testcase里有一个 测试,就是test_string_length。
    整体上就是这么一个架子,suit,test case, test都可以往里扩展。
    1. #include <assert.h>
    2. #include <stdlib.h>
    3. #include <string.h>

    4. #include <CUnit/Basic.h>
    5. #include <CUnit/Console.h>
    6. #include <CUnit/CUnit.h>
    7. #include <CUnit/TestDB.h>

    8. // 测试库函数strlen功能是否正常
    9. void test_string_lenth(void){
    10.     char* test = "Hello";
    11.     int len = strlen(test);
    12.     CU_ASSERT_EQUAL(len,5);
    13. }

    14. // 创建一特test case,里面可以有多个测试
    15. CU_TestInfo testcase[] = {
    16.     { "test_for_lenth:", test_string_lenth },
    17.     CU_TEST_INFO_NULL
    18. };

    19. // suite初始化,
    20. int suite_success_init(void) {
    21.     return 0;
    22. }

    23. // suite 清理
    24. int suite_success_clean(void) {
    25.     return 0;
    26. }

    27. // 定义suite集, 里面可以加多个suit
    28. CU_SuiteInfo suites[] = {
    29.     // 以前的版本没有那两个NULL参数,新版需要加上,否则就coredump
    30.     //{"testSuite1", suite_success_init, suite_success_clean, testcase },
    31.     {"testSuite1", suite_success_init, suite_success_clean, NULL, NULL, testcase },
    32.     CU_SUITE_INFO_NULL
    33. };

    34. // 添加测试集, 固定套路
    35. void AddTests(){
    36.     assert(NULL != CU_get_registry());
    37.     assert(!CU_is_test_running());

    38.     if(CUE_SUCCESS != CU_register_suites(suites)){
    39.         exit(EXIT_FAILURE);
    40.     }
    41. }

    42. int RunTest(){
    43.     if(CU_initialize_registry()){
    44.         fprintf(stderr, " Initialization of Test Registry failed. ");
    45.         exit(EXIT_FAILURE);
    46.     }else{
    47.         AddTests();

    48.         // 第一种:直接输出测试结果
    49.         CU_basic_set_mode(CU_BRM_VERBOSE);
    50.         CU_basic_run_tests();

    51.         // 第二种:交互式的输出测试结果
    52.         // CU_console_run_tests();

    53.         // 第三种:自动生成xml,xlst等文件
    54.         //CU_set_output_filename("TestMax");
    55.         //CU_list_tests_to_file();
    56.         //CU_automated_run_tests();

    57.         CU_cleanup_registry();

    58.         return CU_get_error();

    59.     }

    60. }

    61. int main(int argc, char* argv[]) {
    62.     return  RunTest();
    63. }
    复制代码
    然后Makefile里增加如下代码
    1. INC=-I /usr/local/include/CUnit
    2. LIB=-L /usr/local/lib/

    3. test: testcase.c
    4.     gcc -o test.o $(INC) $(LIB) -g  $^ -l cunit
    5.     ./test.o

    6. .PHONY: test
    复制代码
    再执行make test就可以执行单元测试了,结果大约如下
    1. gcc -o test.o -I /usr/local/include/CUnit -L /usr/local/lib/ -g  testcase.c -l cunit
    2. ./test.o


    3.      CUnit - A unit testing framework for C - Version 2.1-3
    4.      http://cunit.sourceforge.net/


    5. Suite: testSuite1
    6.   Test: test_for_lenth: ...passed

    7. Run Summary:    Type  Total    Ran Passed Failed Inactive
    8.               suites      1      1    n/a      0        0
    9.                tests      1      1      1      0        0
    10.              asserts      1      1      1      0      n/a

    11. Elapsed time =    0.000 seconds
    复制代码
    可以看到testSuite1下面的test_for_lenth通过测试了。 注意一下,安装完新的动态库后记得ldconfig,否则-l cunit可能会报错 如果还是不行就要 /etc/ld.so.conf 看看有没有 /usr/local/lib , cunit默认把库都放这里了。
    调试coredump
    就上面的单元测试, 如果使用注释掉那行,执行make test时就会产生coredump。如下
    1. // 定义suite集, 里面可以加多个suit
    2. CU_SuiteInfo suites[] = {
    3.     {"testSuite1", suite_success_init, suite_success_clean, testcase },
    4.     //{"testSuite1", suite_success_init, suite_success_clean, NULL, NULL, testcase },
    5.     CU_SUITE_INFO_NULL
    6. };
    复制代码
    但默认coredump不会保存在磁盘上,需要执ulimit -c unlimited才可以,然后要 指定一下coredump的路径和格式:
    1. echo "/tmp/core-%e-%p" > /proc/sys/kernel/core_pattern
    复制代码
    其中%e是可执行文件名,%p是进程id。然后编译这段代码的时候要加上-g的选项,意思 是编译出调试版本的可执行文件,在调试的时候可以看到行号。
    1. gcc -o test.o -I /usr/local/include/CUnit -L /usr/local/lib/ -g  testcase.c -l cunit
    复制代码
    在执行./test.o后就会产生一个coredump了,比如是/tmp/core-test.o-16793, 这时候 用gdb去调试该coredump,第一个参数是可执行文件,第二个参数是coredump文件
    1. gdb test.o /tmp/core-test.o-16793
    复制代码
    挂上去后默认会有一些输出,其中有如下
    1. Program terminated with signal 11, Segmentation fault.
    复制代码
    说明程序遇到了段错误,崩溃了,一般段错误都是因为内存访问引起的, 我们想知道 引起错误的调用栈, 输入bt回车,会看到类似如下的显示
    1. (gdb) bt
    2. #0  0x00007fe1b0b22cb2 in CU_register_nsuites () from /usr/local/lib/libcunit.so.1
    3. #1  0x00007fe1b0b22d28 in CU_register_suites () from /usr/local/lib/libcunit.so.1
    4. #2  0x0000000000400a8a in AddTests () at testcase.c:46
    5. #3  0x0000000000400adf in RunTest () at testcase.c:56
    6. #4  0x0000000000400b13 in main (argc=1, argv=0x7fff4fa51928) at testcase.c:79
    复制代码
    这样大概知道是咋回事了,报错在testcase.c的46行上,再往里就是cunit的调用栈了, 我们看不到行号,好像得有那个so的调试信息才可以,目前还不会在gdb里动态挂符号文件 ,所以就先不管了,输入q退出调试器,其它命令用输入help学习下。
    1. if(CUE_SUCCESS != CU_register_suites(suites)){
    复制代码
    就调用了一个CU_register_suites函数,函数本身应该没有错误,可能是传给他从参数 有问题,就是那个suites,该参数构建的代码如下:
    1. CU_SuiteInfo suites[] = {
    2.     {"testSuite1", suite_success_init, suite_success_clean, testcase },
    3.     CU_SUITE_INFO_NULL
    4. };
    复制代码
    是个CU_SuiteInfo的数组,就感觉是构建这个类型没构建对,然后就看他在哪儿定义 的
    1. # grep -n "CU_SuiteInfo" /usr/local/include/CUnit/*
    2. /usr/local/include/CUnit/TestDB.h:696:typedef struct CU_SuiteInfo {
    复制代码
    在/usr/local/include/CUnit/TestDB.h的696行,具体如下
    1. typedef struct CU_SuiteInfo {
    2.     const char       *pName;         /**< Suite name. */
    3.     CU_InitializeFunc pInitFunc;     /**< Suite initialization function. */
    4.     CU_CleanupFunc    pCleanupFunc;  /**< Suite cleanup function */
    5.     CU_SetUpFunc      pSetUpFunc;    /**< Pointer to the test SetUp function. */
    6.     CU_TearDownFunc   pTearDownFunc; /**< Pointer to the test TearDown function. */
    7.     CU_TestInfo      *pTests;        /**< Test case array - must be NULL terminated. */
    8. } CU_SuiteInfo;
    复制代码
    可以看到,该结构有6个成员,但我们定义的时候只有4个成员,没有设置pSetUpFunc和 pTearDownFunc的,所以做如下修改就能修复该问题了。
    1. -    {"testSuite1", suite_success_init, suite_success_clean, testcase },
    2. +    {"testSuite1", suite_success_init, suite_success_clean, NULL, NULL, testcase },
    复制代码
    对了,gdb用yum安装就行了。
    性能剖析
    好些时候我们要去分析一个程序的性能,比如哪个函数调用了多少次,被谁调用了, 平均每次调用花费多少时间等。这时候要用gprof,gprof是分析profile输出的。 要想执行时输出profile文件编译时要加-pg选项,
    1. gcc -o helloworld.o -pg -g helloworld.c
    2. ./helloworld.o
    复制代码
    执行上面语句后会在当前目录下生成gmon.out文件, 然后用gprof去读取并显示出来, 因为可能显示的比较长,所以可以先重定向到一个文件prof_info.txt里
    1. gprof -b -A -p -q helloworld.o gmon.out >prof_info.txt
    复制代码
    参数的含义先这么用,具体可以搜,最后查看prof_info.txt里会有需要的信息, 大概 能看懂,具体可以搜。
    1. Flat profile:

    2. Each sample counts as 0.01 seconds.
    3. no time accumulated

    4.   %   cumulative   self              self     total           
    5. time   seconds   seconds    calls  Ts/call  Ts/call  name   
    6.   0.00      0.00     0.00       15     0.00     0.00  cmp_default
    7.   0.00      0.00     0.00       15     0.00     0.00  cmp_reverse
    8.   0.00      0.00     0.00        4     0.00     0.00  w_strlen
    9.   0.00      0.00     0.00        2     0.00     0.00  sort
    10.   0.00      0.00     0.00        1     0.00     0.00  change_str_test
    11.   0.00      0.00     0.00        1     0.00     0.00  concat_test
    12.   0.00      0.00     0.00        1     0.00     0.00  customer_manager
    13.   0.00      0.00     0.00        1     0.00     0.00  hello_world
    14.   0.00      0.00     0.00        1     0.00     0.00  n_hello_world
    15.   0.00      0.00     0.00        1     0.00     0.00  reverse
    16.   0.00      0.00     0.00        1     0.00     0.00  sort_test

    17.             Call graph


    18. granularity: each sample hit covers 2 byte(s) no time propagated

    19. index % time    self    children    called        name
    20.                 0.00    0.00      15/15          sort [4]
    21. [1]      0.0    0.00    0.00      15         cmp_default [1]
    22. -----------------------------------------------
    23.                 0.00    0.00      15/15          sort [4]
    24. [2]      0.0    0.00    0.00      15         cmp_reverse [2]
    25. -----------------------------------------------
    26.                 0.00    0.00       1/4           reverse [10]
    27.                 0.00    0.00       1/4           main [16]
    28.                 0.00    0.00       2/4           concat_test [6]
    29. [3]      0.0    0.00    0.00       4         w_strlen [3]
    30. -----------------------------------------------
    复制代码

    本文来自:http://www.cnblogs.com/onlytiancai/p/3847524.html
    分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友 微信微信
    收藏收藏 分享分享 支持支持 反对反对 微信
    守望者AIR技术交流社区(www.airmyth.com)
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    
    关闭

    站长推荐上一条 /4 下一条

    QQ|手机版|Archiver|网站地图|小黑屋|守望者 ( 京ICP备14061876号

    GMT+8, 2024-4-20 20:39 , Processed in 0.047990 second(s), 34 queries .

    守望者AIR

    守望者AIR技术交流社区

    本站成立于 2014年12月31日

    快速回复 返回顶部 返回列表