快速學習C語言二: 編譯自動化, 靜態分析, 單元測試,coredump調試,性能剖析

上次的Hello world算是入門了,如今學習一些相關工具的使用前端

編譯自動化

寫好程序,首先要編譯,就用gcc就行了,基本用法以下git

gcc helloworld.c -o helloworld.o

helloworld.c是源碼,helloworld.o是編譯後的可執行文件,運行的話就用 ./helloworld.o就能夠了。github

可是若是代碼寫的多了,每次改動完都手動用gcc編譯太麻煩了,因此要用Makefile來 自動化這項工做,在當前目錄下建立Makefile文件,大概以下shell

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每一行表示一個任務,冒號左邊的是目標文件名,冒號後面是生成該目標的依賴 文件,多個的話用逗號隔開,若是依賴文件沒有更改,則不會執行該任務。bootstrap

縮進爲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-3.tar.bz2
cd CUnit-2.1-3
./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,5);
}

// 建立一特test case,裏面能夠有多個測試 
CU_TestInfo testcase[] = {
    { "test_for_lenth:", test_string_lenth },
    CU_TEST_INFO_NULL
};

// suite初始化,
int suite_success_init(void) {
    return 0;
}

// suite 清理
int suite_success_clean(void) {
    return 0;
}

// 定義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-3
     http://cunit.sourceforge.net/


Suite: testSuite1
  Test: test_for_lenth: ...passed

Run Summary:    Type  Total    Ran Passed Failed Inactive
              suites      1      1    n/a      0        0
               tests      1      1      1      0        0
             asserts      1      1      1      0      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-16793

掛上去後默認會有一些輸出,其中有以下

Program terminated with signal 11, Segmentation fault.

說明程序遇到了段錯誤,崩潰了,通常段錯誤都是由於內存訪問引發的, 咱們想知道 引發錯誤的調用棧, 輸入bt回車,會看到相似以下的顯示

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

這樣大概知道是咋回事了,報錯在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       15     0.00     0.00  cmp_default
  0.00      0.00     0.00       15     0.00     0.00  cmp_reverse
  0.00      0.00     0.00        4     0.00     0.00  w_strlen
  0.00      0.00     0.00        2     0.00     0.00  sort
  0.00      0.00     0.00        1     0.00     0.00  change_str_test
  0.00      0.00     0.00        1     0.00     0.00  concat_test
  0.00      0.00     0.00        1     0.00     0.00  customer_manager
  0.00      0.00     0.00        1     0.00     0.00  hello_world
  0.00      0.00     0.00        1     0.00     0.00  n_hello_world
  0.00      0.00     0.00        1     0.00     0.00  reverse
  0.00      0.00     0.00        1     0.00     0.00  sort_test

            Call graph


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

index % time    self  children    called     name
                0.00    0.00      15/15          sort [4]
[1]      0.0    0.00    0.00      15         cmp_default [1]
-----------------------------------------------
                0.00    0.00      15/15          sort [4]
[2]      0.0    0.00    0.00      15         cmp_reverse [2]
-----------------------------------------------
                0.00    0.00       1/4           reverse [10]
                0.00    0.00       1/4           main [16]
                0.00    0.00       2/4           concat_test [6]
[3]      0.0    0.00    0.00       4         w_strlen [3]
-----------------------------------------------
相關文章
相關標籤/搜索