快速学习C语言二: 编译自动化, 静态分析, 单元测试,coredump调试,性能剖析

时间:2022-09-18 19:12:48

上次的Hello world算是入门了,现在学习一些相关工具的使用

编译自动化

写好程序,首先要编译,就用gcc就好了,基本用法如下

gcc helloworld.c -o helloworld.o

helloworld.c是源码,helloworld.o是编译后的可执行文件,运行的话就用 ./helloworld.o就可以了。

但是如果代码写的多了,每次改动完都手动用gcc编译太麻烦了,所以要用Makefile来 自动化这项工作,在当前目录下创建Makefile文件,大概如下

helloworld.o: helloworld.c
gcc helloworld.c -o helloworld.o .PHONY: lint
lint:
splint helloworld.c -temptrans -mustfreefresh -usedef .PHONY: run
run:
./helloworld.o .PHONY: clean
clean:
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

wget http://sourceforge.net/projects/cunit/files/latest/download
tar xf CUnit-2.1-.tar.bz2
cd CUnit-2.1-
./bootstrap
./configure
make
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都可以往里扩展。

#include <assert.h>
#include <stdlib.h>
#include <string.h> #include <CUnit/Basic.h>
#include <CUnit/Console.h>
#include <CUnit/CUnit.h>
#include <CUnit/TestDB.h> // 测试库函数strlen功能是否正常
void test_string_lenth(void){
char* test = "Hello";
int len = strlen(test);
CU_ASSERT_EQUAL(len,);
} // 创建一特test case,里面可以有多个测试
CU_TestInfo testcase[] = {
{ "test_for_lenth:", test_string_lenth },
CU_TEST_INFO_NULL
}; // suite初始化,
int suite_success_init(void) {
return ;
} // suite 清理
int suite_success_clean(void) {
return ;
} // 定义suite集, 里面可以加多个suit
CU_SuiteInfo suites[] = {
// 以前的版本没有那两个NULL参数,新版需要加上,否则就coredump
//{"testSuite1", suite_success_init, suite_success_clean, testcase },
{"testSuite1", suite_success_init, suite_success_clean, NULL, NULL, testcase },
CU_SUITE_INFO_NULL
}; // 添加测试集, 固定套路
void AddTests(){
assert(NULL != CU_get_registry());
assert(!CU_is_test_running()); if(CUE_SUCCESS != CU_register_suites(suites)){
exit(EXIT_FAILURE);
}
} int RunTest(){
if(CU_initialize_registry()){
fprintf(stderr, " Initialization of Test Registry failed. ");
exit(EXIT_FAILURE);
}else{
AddTests(); // 第一种:直接输出测试结果
CU_basic_set_mode(CU_BRM_VERBOSE);
CU_basic_run_tests(); // 第二种:交互式的输出测试结果
// CU_console_run_tests(); // 第三种:自动生成xml,xlst等文件
//CU_set_output_filename("TestMax");
//CU_list_tests_to_file();
//CU_automated_run_tests(); CU_cleanup_registry(); return CU_get_error(); } } int main(int argc, char* argv[]) {
return RunTest();
}

然后Makefile里增加如下代码

INC=-I /usr/local/include/CUnit
LIB=-L /usr/local/lib/ test: testcase.c
gcc -o test.o $(INC) $(LIB) -g $^ -l cunit
./test.o .PHONY: test

再执行make test就可以执行单元测试了,结果大约如下

gcc -o test.o -I /usr/local/include/CUnit -L /usr/local/lib/ -g  testcase.c -l cunit
./test.o CUnit - A unit testing framework for C - Version 2.1-
http://cunit.sourceforge.net/ Suite: testSuite1
Test: test_for_lenth: ...passed Run Summary: Type Total Ran Passed Failed Inactive
suites n/a
tests
asserts n/a Elapsed time = 0.000 seconds

可以看到testSuite1下面的test_for_lenth通过测试了。 注意一下,安装完新的动态库后记得ldconfig,否则-l cunit可能会报错 如果还是不行就要 /etc/ld.so.conf 看看有没有 /usr/local/lib , cunit默认把库都放这里了。

调试coredump

就上面的单元测试, 如果使用注释掉那行,执行make test时就会产生coredump。如下

// 定义suite集, 里面可以加多个suit
CU_SuiteInfo suites[] = {
{"testSuite1", suite_success_init, suite_success_clean, testcase },
//{"testSuite1", suite_success_init, suite_success_clean, NULL, NULL, testcase },
CU_SUITE_INFO_NULL
};

但默认coredump不会保存在磁盘上,需要执ulimit -c unlimited才可以,然后要 指定一下coredump的路径和格式:

echo "/tmp/core-%e-%p" > /proc/sys/kernel/core_pattern

其中%e是可执行文件名,%p是进程id。然后编译这段代码的时候要加上-g的选项,意思 是编译出调试版本的可执行文件,在调试的时候可以看到行号。

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文件

gdb test.o /tmp/core-test.o-

挂上去后默认会有一些输出,其中有如下

Program terminated with signal , Segmentation fault.

说明程序遇到了段错误,崩溃了,一般段错误都是因为内存访问引起的, 我们想知道 引起错误的调用栈, 输入bt回车,会看到类似如下的显示

(gdb) bt
# 0x00007fe1b0b22cb2 in CU_register_nsuites () from /usr/local/lib/libcunit.so.
# 0x00007fe1b0b22d28 in CU_register_suites () from /usr/local/lib/libcunit.so.
# 0x0000000000400a8a in AddTests () at testcase.c:
# 0x0000000000400adf in RunTest () at testcase.c:
# 0x0000000000400b13 in main (argc=, argv=0x7fff4fa51928) at testcase.c:

这样大概知道是咋回事了,报错在testcase.c的46行上,再往里就是cunit的调用栈了, 我们看不到行号,好像得有那个so的调试信息才可以,目前还不会在gdb里动态挂符号文件 ,所以就先不管了,输入q退出调试器,其它命令用输入help学习下。

if(CUE_SUCCESS != CU_register_suites(suites)){

就调用了一个CU_register_suites函数,函数本身应该没有错误,可能是传给他从参数 有问题,就是那个suites,该参数构建的代码如下:

CU_SuiteInfo suites[] = {
{"testSuite1", suite_success_init, suite_success_clean, testcase },
CU_SUITE_INFO_NULL
};

是个CU_SuiteInfo的数组,就感觉是构建这个类型没构建对,然后就看他在哪儿定义 的

# grep -n "CU_SuiteInfo" /usr/local/include/CUnit/*
/usr/local/include/CUnit/TestDB.h:696:typedef struct CU_SuiteInfo {

在/usr/local/include/CUnit/TestDB.h的696行,具体如下

typedef struct CU_SuiteInfo {
const char *pName; /**< Suite name. */
CU_InitializeFunc pInitFunc; /**< Suite initialization function. */
CU_CleanupFunc pCleanupFunc; /**< Suite cleanup function */
CU_SetUpFunc pSetUpFunc; /**< Pointer to the test SetUp function. */
CU_TearDownFunc pTearDownFunc; /**< Pointer to the test TearDown function. */
CU_TestInfo *pTests; /**< Test case array - must be NULL terminated. */
} CU_SuiteInfo;

可以看到,该结构有6个成员,但我们定义的时候只有4个成员,没有设置pSetUpFunc和 pTearDownFunc的,所以做如下修改就能修复该问题了。

-    {"testSuite1", suite_success_init, suite_success_clean, testcase },
+ {"testSuite1", suite_success_init, suite_success_clean, NULL, NULL, testcase },

对了,gdb用yum安装就行了。

性能剖析

好些时候我们要去分析一个程序的性能,比如哪个函数调用了多少次,被谁调用了, 平均每次调用花费多少时间等。这时候要用gprof,gprof是分析profile输出的。 要想执行时输出profile文件编译时要加-pg选项,

gcc -o helloworld.o -pg -g helloworld.c
./helloworld.o

执行上面语句后会在当前目录下生成gmon.out文件, 然后用gprof去读取并显示出来, 因为可能显示的比较长,所以可以先重定向到一个文件prof_info.txt里

gprof -b -A -p -q helloworld.o gmon.out >prof_info.txt 

参数的含义先这么用,具体可以搜,最后查看prof_info.txt里会有需要的信息, 大概 能看懂,具体可以搜。

Flat profile:

Each sample counts as 0.01 seconds.
no time accumulated % cumulative self self total
time seconds seconds calls Ts/call Ts/call name
0.00 0.00 0.00 0.00 0.00 cmp_default
0.00 0.00 0.00 0.00 0.00 cmp_reverse
0.00 0.00 0.00 0.00 0.00 w_strlen
0.00 0.00 0.00 0.00 0.00 sort
0.00 0.00 0.00 0.00 0.00 change_str_test
0.00 0.00 0.00 0.00 0.00 concat_test
0.00 0.00 0.00 0.00 0.00 customer_manager
0.00 0.00 0.00 0.00 0.00 hello_world
0.00 0.00 0.00 0.00 0.00 n_hello_world
0.00 0.00 0.00 0.00 0.00 reverse
0.00 0.00 0.00 0.00 0.00 sort_test Call graph granularity: each sample hit covers byte(s) no time propagated index % time self children called name
0.00 0.00 / sort []
[] 0.0 0.00 0.00 cmp_default []
-----------------------------------------------
0.00 0.00 / sort []
[] 0.0 0.00 0.00 cmp_reverse []
-----------------------------------------------
0.00 0.00 / reverse []
0.00 0.00 / main []
0.00 0.00 / concat_test []
[] 0.0 0.00 0.00 w_strlen []
-----------------------------------------------

快速学习C语言二: 编译自动化, 静态分析, 单元测试,coredump调试,性能剖析的更多相关文章

  1. 快速学习C语言一&colon; Hello World

    估计不会写C语言的同学也都听过C语言,从头开始快速学一下吧,以后肯定能用的上. 如果使用过其它类C的语言,如JAVA,C#等,学C的语法应该挺快的. 先快速学习并练习一些基本的语言要素,基本类型,表达 ...

  2. 快速学习C语言三&colon; 开发环境&comma; VIM配置&comma; TCP基础,Linux开发基础,Socket开发基础

    上次学了一些C开发相关的工具,这次再配置一下VIM,让开发过程更爽一些. 另外再学一些linux下网络开发的基础,好多人学C也是为了做网络开发. 开发环境 首先得有个Linux环境,有时候家里机器是W ...

  3. 快速学习C语言途径,让你少走弯路

    1.标准C语言能干什么? 坦白讲,在今天软件已经发展了半个多世纪,单纯的C语言什么都干不了.标准C语言库只提供了一些通用的逻辑运算方法以及字符串处理,当然字符串在C语言看来也是一种操作内存的方法,所以 ...

  4. scala快速学习笔记(二):控制结构,类和对象

    IV.控制结构 1.if/else 除基本用法外,if/else语句能用来赋值,进而代替?:运算符.这得益于在Scala中,每个语句块都有值,就是该语句块最后一个语句的值.请看下面的代码. def a ...

  5. 快速学习C语言四&colon; 造*,ArrayList

    高级语言里的列表是最常用的数据结构,在C里造个*玩玩,C没有泛型,先用int练习. Collection的ADT一般有hasnext,next,add, remove操作,List一般还加了remo ...

  6. Dynamic CRM 2013学习笔记(二)插件基本用法及调试

      插件是可与 Microsoft Dynamics CRM 2013 和 Microsoft Dynamics CRM Online 集成的自定义业务逻辑(代码),用于修改或增加平台的标准行为.也可 ...

  7. Go学习笔记(二)搭建Visual Studio Code调试环境

    上一篇 Go学习笔记(一)安装Go语言环境 安装Visual Studio Code 这是目前我觉得最好用的文本编辑器了, https://code.visualstudio.com/ 中间有几部确认 ...

  8. 学习swift语言的快速入门教程推荐

    随着苹果产品越来越火爆,苹果新推出的swift必定将在很大程度上代替oc语言.学好swift语言,对于IOS工程师来讲,已经是一门必备技能. 有一些比较好的英文版教程,值得学习. 1. Swift T ...

  9. Dart语言快速学习上手(新手上路)

    Dart语言快速学习上手(新手上路) // 声明返回值 int add(int a, int b) { return a + b; } // 不声明返回值 add2(int a, int b) { r ...

随机推荐

  1. C语言课程学习的总结

    C语言课程学习的总结 学习C程序这门课一年了,这是我们学的第一门专业课.在大学里,C语言不但是计算机专业的必修课程而且也是非计算机专业学习计算机基础的一门必修课程.所以作为我这个计算机专业的学生来说当 ...

  2. Set接口

    Set接口也是Collection接口的子接口,Set接口中不能加入重复的元素 Set接口的常用子类 1.散列的存放:HashSet HashSet是Set接口的一个子类,主要的特点是:里面不能存放重 ...

  3. postgresql数据库实用操作

    查模型的列名: select column_name from information_schema.columns  where table_name= 'your_table'; 应用: 1. 给 ...

  4. python 操作execl文件

    http://www.jb51.net/article/60510.htm import xlrdimport xlwt # 打开文件   workbook = xlrd.open_workbook( ...

  5. (1)创建一个叫做机动车的类: 属性:车牌号&lpar;String&rpar;,车速&lpar;int&rpar;,载重量&lpar;double&rpar; 功能:加速&lpar;车速自增&rpar;、减速&lpar;车速自减&rpar;、修改车牌号,查询车的载重量。 编写两个构造方法:一个没有形参,在方法中将车牌号设置&OpenCurlyDoubleQuote;XX1234”,速 度设置为100,载重量设置为100;另一个能为对象的所有属性赋值; (2)创建主类: 在主类中创建两个机动车对象。

    package a; public class Jidongche { private String chepaihao; private int chesu; private double zaiz ...

  6. Redis pipeline and list

    Redis Redis 是一个开源的基于内存的数据结构存储器.通常可作为数据库,缓存和消息中介.它支持的数据结构有:字符串.哈希表.列表.集合.支持范围查询的有序集合.位图.hyperloglogs和 ...

  7. 【背包型动态规划】灵魂分流药剂&lpar;soultap&rpar; 解题报告

    问题来源 BYVoid魔兽世界模拟赛 [问题描述] 皇家炼金师赫布瑞姆刚刚发明了一种用来折磨一切生物的新产品,灵魂分流药剂.灵魂分流药剂的妙处在于能够给服用者带来巨大的痛苦,但是却不会让服用者死去,而 ...

  8. IOS 调用系统相册或照相机tab按钮显示中文

  9. nRF Toolbox 1&period;2 使用AKII的实现,而Becon始终不好使

    这几天调试使用nRF51822驱动mpu6050及其数据传输到android中,调试的过程遇到一些困难,apptimer不太会用,然后就參考了下ble_app_hrs的程序,结果成功搞定,demo的价 ...

  10. 对中级 Linux 用户有用的 20 个命令

    也许你已经发现第一篇文章非常的有用,这篇文章是继对初级Linux用户非常有用的20个命令的一个延伸. 第一篇文章的目的是为新手准备的而这篇文章则是为了Linux的中高级用户.在这里你将学会如何进行自定 ...