<dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><small id="yhprb"></small><dfn id="yhprb"></dfn><small id="yhprb"><delect id="yhprb"></delect></small><small id="yhprb"></small><small id="yhprb"></small> <delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"></dfn><dfn id="yhprb"></dfn><s id="yhprb"><noframes id="yhprb"><small id="yhprb"><dfn id="yhprb"></dfn></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><small id="yhprb"></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn> <small id="yhprb"></small><delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn>

新聞中心

EEPW首頁(yè) > 設計應用 > 嵌入式軟件開(kāi)發(fā)常用的套路與技巧

嵌入式軟件開(kāi)發(fā)常用的套路與技巧

作者: 時(shí)間:2023-12-12 來(lái)源: 收藏

01. 調試相關(guān)的宏

在Linux使用gcc編譯程序的時(shí)候,對于調試的語(yǔ)句還具有一些特殊的語(yǔ)法。

本文引用地址:http://dyxdggzs.com/article/202312/453840.htm

gcc編譯的過(guò)程中,會(huì )生成一些宏,可以使用這些宏分別打印當前源文件的信息,主要內容是當前的文件、當前運行的函數和當前的程序行。

具體宏如下:

__FILE__  當前程序源文件 (char*)
__FUNCTION__  當前運行的函數 (char*)
__LINE__  當前的函數行 (int)

這些宏不是程序代碼定義的,而是有編譯器產(chǎn)生的。這些信息都是在編譯器處理文件的時(shí)候動(dòng)態(tài)產(chǎn)生的。

「測試示例:」

#include 
int main(void){
    printf("file: %sn", __FILE__);
    printf("function: %sn", __FUNCTION__);
    printf("line: %dn", __LINE__);
    return 0;
}

02. # 字符串化操作符

在gcc的編譯系統中,可以使用#將當前的內容轉換成字符串。

「程序示例:」

#include 
#define DPRINT(expr) printf("
%s = %dn", #expr, expr);
int main(void){
    int x = 3;
    int y = 5;
    DPRINT(x / y);
    DPRINT(x + y);
    DPRINT(x * y);
    
    return 0;
}

「執行結果:」

deng@itcast:~/tmp$ gcc test.c 
deng@itcast:~/tmp$ ./a.out  
x / y = 0x + y = 8x * y = 15

#expr表示根據宏中的參數(即表達式的內容),生成一個(gè)字符串。該過(guò)程同樣是有編譯器產(chǎn)生的,編譯器在編譯源文件的時(shí)候,如果遇到了類(lèi)似的宏,會(huì )自動(dòng)根據程序中表達式的內容,生成一個(gè)字符串的宏。

這種方式的優(yōu)點(diǎn)是可以用統一的方法打印表達式的內容,在程序的調試過(guò)程中可以方便直觀(guān)的看到轉換字符串之后的表達式。

具體的表達式的內容是什么,有編譯器自動(dòng)寫(xiě)入程序中,這樣使用相同的宏打印所有表達式的字符串。

//打印字符
#define debugc(expr) printf(" %s = %cn", #expr, expr)
//打印浮點(diǎn)數
#define debugf(expr) printf(" %s = %fn", #expr, expr)
//按照16進(jìn)制打印整數
#define debugx(expr) printf(" %s = 0X%xn", #expr, expr);

由于#expr本質(zhì)上市一個(gè)表示字符串的宏,因此在程序中也可以不適用%s打印它的內容,而是可以將其直接與其它的字符串連接。

因此,上述宏可以等價(jià)以下形式:

//打印字符
#define debugc(expr) printf(" #expr = %cn", expr)
//打印浮點(diǎn)數
#define debugf(expr) printf(" #expr = %fn", expr)
//按照16進(jìn)制打印整數
#define debugx(expr) printf(" #expr = 0X%xn", expr);

「總結:」

#是C語(yǔ)言預處理階段的字符串化操作符,可將宏中的內容轉換成字符串。

03. ## 連接操作符

在gcc的編譯系統中,##是C語(yǔ)言中的連接操作符,可以在編譯的預處理階段實(shí)現字符串連接的操作。

「程序示例:」

#include 
#define test(x) test##x
void test1(int a){
    printf("test1 a = %dn", a);
}
void test2(char *s){
    printf("test2 s = %sn", s);
}
int main(void){
    test(1)(100);
    test(2)("hello world");
    
    return 0;
}

上述程序中,test(x)宏被定義為test##x, 他表示test字符串和x字符串的連接。

在程序的調試語(yǔ)句中,##常用的方式如下

#define DEBUG(fmt, args...) printf(fmt, ##args)

替換的方式是將參數的兩個(gè)部分以##連接。##表示連接變量代表前面的參數列表。使用這種形式可以將宏的參數傳遞給一個(gè)參數。args…是宏的參數,表示可變的參數列表,使用##args將其傳給printf函數.

「總結:」

##是C語(yǔ)言預處理階段的連接操作符,可實(shí)現宏參數的連接。

04. 調試宏第一種形式

一種定義的方式:

#define DEBUG(fmt, args...)             
    {                                   
    printf("file:%s function: %s line: %d ", __FILE__, __FUNCTION__, __LINE__);
    printf(fmt, ##args);                
    }

「程序示例:」

#include 
#define DEBUG(fmt, args...)             
    {                                   
    printf("file:%s function: %s line: %d ", __FILE__, __FUNCTION__, __LINE__);
    printf(fmt, ##args);                
    }

int main(void){
    int a = 100;
    int b = 200;
    char *s = "hello world";
    DEBUG("a = %d b = %dn", a, b);
    DEBUG("a = %x b = %xn", a, b);
    DEBUG("s = %sn", s);
    
    return 0;
}

「總結:」

上面的DEBUG定義的方式是兩條語(yǔ)句的組合,不可能在產(chǎn)生返回值,因此不能使用它的返回值。

05. 調試宏的第二種定義方式

調試宏的第二種定義方式

#define DEBUG(fmt, args...)             
    printf("file:%s function: %s line: %d "fmt, 
    __FILE__, __FUNCTION__, __LINE__, ##args)

程序示例

#include 
#define DEBUG(fmt, args...)             
    printf("file:%s function: %s line: %d "fmt, 
    __FILE__, __FUNCTION__, __LINE__, ##args)

int main(void){
    int a = 100;
    int b = 200;
    char *s = "hello world";
    DEBUG("a = %d b = %dn", a, b);
    DEBUG("a = %x b = %xn", a, b);
    DEBUG("s = %sn", s);
    
    return 0;
}

「總結:」

fmt必須是一個(gè)字符串,不能使用指針,只有這樣才可以實(shí)現字符串的功能。

06. 對調試語(yǔ)句進(jìn)行分級審查

即使定義了調試的宏,在工程足夠大的情況下,也會(huì )導致在打開(kāi)宏開(kāi)關(guān)的時(shí)候在終端出現大量的信息。而無(wú)法區分哪些是有用的。

這個(gè)時(shí)候就要加入分級檢查機制,可以定義不同的調試級別,這樣就可以對不同重要程序和不同的模塊進(jìn)行區分,需要調試哪一個(gè)模塊就可以打開(kāi)那一個(gè)模塊的調試級別。

一般可以利用配置文件的方式顯示,其實(shí)Linux內核也是這么做的,它把調試的等級分成了7個(gè)不同重要程度的級別,只有設定某個(gè)級別可以顯示,對應的調試信息才會(huì )打印到終端上。

可以寫(xiě)出一下配置文件

[debug]
debug_level=XXX_MODULE

解析配置文件使用標準的字符串操作庫函數就可以獲取XXX_MODULE這個(gè)數值。

int show_debug(int level){
    if (level == XXX_MODULE)
    {
        #define DEBUG(fmt, args...)             
        printf("file:%s function: %s line: %d "fmt, 
        __FILE__, __FUNCTION__, __LINE__, ##args)       

    }
    else if (...)
    {
        ....
    }
}

07. 條件編譯調試語(yǔ)句

在實(shí)際的開(kāi)發(fā)中,一般會(huì )維護兩種源程序,一種是帶有調試語(yǔ)句的調試版本程序,另外一種是不帶有調試語(yǔ)句的發(fā)布版本程序。

然后根據不同的條件編譯選項,編譯出不同的調試版本和發(fā)布版本的程序。

在實(shí)現過(guò)程中,可以使用一個(gè)調試宏來(lái)控制調試語(yǔ)句的開(kāi)關(guān)。

#ifdef USE_DEBUG
        #define DEBUG(fmt, args...)             
        printf("file:%s function: %s line: %d "fmt, 
        __FILE__, __FUNCTION__, __LINE__, ##args)  

#else
  #define DEBUG(fmt, args...)
#endif

如果USE_DEBUG被定義,那么有調試信息,否則DEBUG就為空。

如果需要調試信息,就只需要在程序中更改一行就可以了。

#define USE_DEBUG
#undef USE_DEBUG

定義條件編譯的方式使用一個(gè)帶有值的宏

#if USE_DEBUG
        #define DEBUG(fmt, args...)             
        printf("file:%s function: %s line: %d "fmt, 
        __FILE__, __FUNCTION__, __LINE__, ##args)  

#else
  #define DEBUG(fmt, args...)
#endif

可以使用如下方式進(jìn)行條件編譯

#ifndef USE_DEBUG
#define USE_DEBUG 0
#endif

08. 使用do…while的宏定義

使用宏定義可以將一些較為短小的功能封裝,方便使用。宏的形式和函數類(lèi)似,但是可以節省函數跳轉的開(kāi)銷(xiāo)。

如何將一個(gè)語(yǔ)句封裝成一個(gè)宏,在程序中常常使用do…while(0)的形式。

#define HELLO(str) do { 
printf("hello: %sn", str); 
}while(0)

「程序示例:」

int cond = 1;
if (cond)
    HELLO("true");
else
    HELLO("false");

09. 代碼剖析

對于比較大的程序,可以借助一些工具來(lái)首先把需要優(yōu)化的點(diǎn)清理出來(lái)。接下來(lái)我們來(lái)看看在程序執行過(guò)程中獲取數據并進(jìn)行分析的工具:代碼剖析程序。

「測試程序:」

#include 
#define T 100000
void call_one(){
    int count = T * 1000;
    while(count--);
}
void call_two(){
    int count = T * 50;
    while(count--);
}
void call_three(){
    int count = T * 20;
    while(count--);
}
int main(void){
    int time = 10;
    while(time--)
    {
        call_one();
        call_two();
        call_three();
    }
    
    return 0;
}

編譯的時(shí)候加入-pg選項:

deng@itcast:~/tmp$ gcc -pg  test.c -o test

執行完成后,在當前文件中生成了一個(gè)gmon.out文件。

deng@itcast:~/tmp$ ./test  
deng@itcast:~/tmp$ ls
gmon.out  test  test.c
deng@itcast:~/tmp$ 

「使用gprof剖析主程序:」

deng@itcast:~/tmp$ gprof test
Flat profile:
Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total           
 time   seconds   seconds    calls  ms/call  ms/call  name    
 95.64      1.61     1.61       10   160.68   160.68  call_one
  3.63      1.67     0.06       10     6.10     6.10  call_two
  2.42      1.71     0.04       10     4.07     4.07  call_three

其中主要的信息有兩個(gè),一個(gè)是每個(gè)函數執行的時(shí)間占程序總時(shí)間的百分比,另外一個(gè)就是函數被調用的次數。通過(guò)這些信息,可以?xún)?yōu)化核心程序的實(shí)現方式來(lái)提高效率。

當然這個(gè)剖析程序由于它自身特性有一些限制,比較適用于運行時(shí)間比較長(cháng)的程序,因為統計的時(shí)間是基于間隔計數這種機制,所以還需要考慮函數執行的相對時(shí)間,如果程序執行時(shí)間過(guò)短,那得到的信息是沒(méi)有任何參考意義的。

「將上訴程序時(shí)間縮短:」

#include 
#define T 100
void call_one(){
    int count = T * 1000;
    while(count--);
}
void call_two(){
    int count = T * 50;
    while(count--);
}
void call_three(){
    int count = T * 20;
    while(count--);
}
int main(void){
    int time = 10;
    while(time--)
    {
        call_one();
        call_two();
        call_three();
    }
    
    return 0;
}

「剖析結果如下:」

deng@itcast:~/tmp$ gcc -pg test.c -o test
deng@itcast:~/tmp$ ./test  
deng@itcast:~/tmp$ gprof test
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       10     0.00     0.00  call_one
  0.00      0.00     0.00       10     0.00     0.00  call_three
  0.00      0.00     0.00       10     0.00     0.00  call_two

因此該剖析程序對于越復雜、執行時(shí)間越長(cháng)的函數也適用。

那么是不是每個(gè)函數執行的絕對時(shí)間越長(cháng),剖析顯示的時(shí)間就真的越長(cháng)呢?可以再看如下的例子

#include 
#define T 100
void call_one(){
    int count = T * 1000;
    while(count--);
}
void call_two(){
    int count = T * 100000;
    while(count--);
}
void call_three(){
    int count = T * 20;
    while(count--);
}
int main(void){
    int time = 10;
    while(time--)
    {
        call_one();
        call_two();
        call_three();
    }
    
    return 0;
}

「剖析結果如下:」

deng@itcast:~/tmp$ gcc -pg test.c -o test
deng@itcast:~/tmp$ ./test  
deng@itcast:~/tmp$ gprof test
Flat profile:
Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total           
 time   seconds   seconds    calls  ms/call  ms/call  name    
101.69      0.15     0.15       10    15.25    15.25  call_two
  0.00      0.15     0.00       10     0.00     0.00  call_one
  0.00      0.15     0.00       10     0.00     0.00  call_three

「總結:」

在使用gprof工具的時(shí)候,對于一個(gè)函數進(jìn)行g(shù)prof方式的剖析,實(shí)質(zhì)上的時(shí)間是指除去庫函數調用和系統調用之外,純碎應用部分開(kāi)發(fā)的實(shí)際代碼運行的時(shí)間,也就是說(shuō)time一項描述的時(shí)間值不包括庫函數printf、系統調用system等運行的時(shí)間。

這些實(shí)用庫函數的程序雖然運行的時(shí)候將比最初的程序實(shí)用更多的時(shí)間,但是對于剖析函數來(lái)說(shuō)并沒(méi)有影響。



關(guān)鍵詞: 嵌入式 軟件開(kāi)發(fā)

評論


相關(guān)推薦

技術(shù)專(zhuān)區

關(guān)閉
国产精品自在自线亚洲|国产精品无圣光一区二区|国产日产欧洲无码视频|久久久一本精品99久久K精品66|欧美人与动牲交片免费播放
<dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><small id="yhprb"></small><dfn id="yhprb"></dfn><small id="yhprb"><delect id="yhprb"></delect></small><small id="yhprb"></small><small id="yhprb"></small> <delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"></dfn><dfn id="yhprb"></dfn><s id="yhprb"><noframes id="yhprb"><small id="yhprb"><dfn id="yhprb"></dfn></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><small id="yhprb"></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn> <small id="yhprb"></small><delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn>