真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

嵌入式C語(yǔ)言自我修養(yǎng)12:有一種宏,叫可變參數(shù)宏

12.1 什么是可變參數(shù)宏

在上面的教程中,我們學(xué)會(huì)了變參函數(shù)的定義和使用,基本套路就是使用 va_list、va_start、va_end 等宏,去解析那些可變參數(shù)列表我們找到這些參數(shù)的存儲(chǔ)地址后,就可以對(duì)這些參數(shù)進(jìn)行處理了:要么自己動(dòng)手,自己處理;要么繼續(xù)調(diào)用其它函來(lái)處理。

成都創(chuàng)新互聯(lián)公司專注于惠城企業(yè)網(wǎng)站建設(shè),成都響應(yīng)式網(wǎng)站建設(shè),電子商務(wù)商城網(wǎng)站建設(shè)。惠城網(wǎng)站建設(shè)公司,為惠城等地區(qū)提供建站服務(wù)。全流程定制制作,專業(yè)設(shè)計(jì),全程項(xiàng)目跟蹤,成都創(chuàng)新互聯(lián)公司專業(yè)和態(tài)度為您提供的服務(wù)

void print_num(int count, ...)
{
    va_list args;
    va_start(args,count);
    for(int i = 0; i < count; i++)
    {
        printf("*args: %d\n",*(int *)args);
        args += 4; 
    }
}
void __attribute__((format(printf,2,3))) LOG(int k,char *fmt,...)
{
    va_list args;
    va_start(args,fmt);
    vprintf(fmt,args);
    va_end(args);
}

GNU C 覺(jué)得這樣不過(guò)癮,再來(lái)個(gè)猛錘:干脆宏定義也支持變參吧!

這一節(jié)我們要學(xué)習(xí)一下可變參數(shù)宏的定義和使用。其實(shí),C99 標(biāo)準(zhǔn)已經(jīng)支持了這個(gè)特性,但是其它的編譯器不太給力,對(duì) C99 標(biāo)準(zhǔn)的支持不是很好,只有 GNU C 支持這個(gè)功能,所以有時(shí)候我們也把這個(gè)可變參數(shù)宏看作是 GNU C 的一個(gè)語(yǔ)法擴(kuò)展。 上面的 LOG 函數(shù),如果我們想使用一個(gè)變參宏實(shí)現(xiàn),就可以直接這樣定義。

#define LOG(fmt, ...) printf(fmt, __VA_ARGS__)

#define DEBUG(...) printf(__VA_ARGS__)

int main(void)
{
    LOG("Hello! I'm %s\n","Wanglitao");
    DEBUG("Hello! I'm %s\n","Wanglitao");
    return 0;
}

變參宏的實(shí)現(xiàn)形式其實(shí)跟變參函數(shù)差不多:用 ... 表示變參列表,變參列表由不確定的參數(shù)組成,各個(gè)參數(shù)之間用逗號(hào)隔開??勺儏?shù)宏使用 C99 標(biāo)準(zhǔn)新增加的一個(gè) VA_ARGS__ 預(yù)定義標(biāo)識(shí)符來(lái)表示前面的變參列表,而不是像變參函數(shù)一樣,使用 va_list、va_start、va_end 這些宏去解析變參列表。預(yù)處理器在將宏展開時(shí),會(huì)用變參列表替換掉宏定義中的所有VA_ARGS__ 標(biāo)識(shí)符。

使用宏定義實(shí)現(xiàn)一個(gè)變參打印功能,你會(huì)發(fā)現(xiàn),它的實(shí)現(xiàn)甚至比變參函數(shù)還方便!內(nèi)核中的很多打印宏,經(jīng)常使用可變參數(shù)宏來(lái)實(shí)現(xiàn),宏定義一般為下面這個(gè)格式。

#define LOG(fmt, ...) printf(fmt, __VA_ARGS__)

在這個(gè)宏定義中,有一個(gè)固定參數(shù),通常為一個(gè)格式字符串,后面的變參用來(lái)打印各種格式的數(shù)據(jù),跟前面的格式字符串相匹配。這種定義方式有一個(gè)漏洞,即當(dāng)變參為空時(shí),宏展開時(shí)就會(huì)產(chǎn)生一個(gè)語(yǔ)法錯(cuò)誤。

#define LOG(fmt,...) printf(fmt,__VA_ARGS__)
int main(void)
{
    LOG("hello\n");
    return 0;
}

上面這個(gè)程序編譯時(shí)就會(huì)通不過(guò),產(chǎn)生一個(gè)語(yǔ)法錯(cuò)誤。這是因?yàn)?,我們只給 LOG 宏傳遞了一個(gè)參數(shù),而變參為空。當(dāng)宏展開后,就變成了下面這個(gè)樣子。

printf("hello\n", );

宏展開后,在第一個(gè)字符串參數(shù)的后面還有一個(gè)逗號(hào),所以就產(chǎn)生了一個(gè)語(yǔ)法錯(cuò)誤。我們需要繼續(xù)對(duì)這個(gè)宏進(jìn)行改進(jìn),使用宏連接符 ##,來(lái)避免這個(gè)語(yǔ)法錯(cuò)誤。

12.2 繼續(xù)改進(jìn)我們的宏

我們接下來(lái),使用宏連接符 ## 來(lái)改進(jìn)上面的宏。

宏連接符 ## 的主要作用就是連接兩個(gè)字符串,我們?cè)诤甓x中可以使用 ## 來(lái)連接兩個(gè)字符。預(yù)處理器在預(yù)處理階段對(duì)宏展開時(shí),會(huì)將 ## 兩邊的字符合并,并刪除 ## 這兩個(gè)字符。

#define A(x) a##x
int main(void)
{
    int A(1) = 2; //int a1 = 2;
    int A() = 3;  //int a=3;
    printf("%d %d\n",a1,a);
    return 0;   
}

如上面的程序,我們定義一個(gè)宏。

#define A(x) a##x

這個(gè)宏的功能就是連接字符 a 和 x。在程序中,A(1) 展開后就是 a1,A( ) 展開后就是 a。我們使用 printf( ) 函數(shù)可以直接打印變量 a1、a 的值,因?yàn)楹暾归_后,就相當(dāng)于使用 int 關(guān)鍵字定義了兩個(gè)整型變量 a1 和 a。上面的程序可以編譯通過(guò),運(yùn)行結(jié)果如下。

2  3

知道了宏連接符 ## 的使用方法,我們接下來(lái)就可以就對(duì) LOG 宏做一些修改。

#define LOG(fmt,...) printf(fmt, ##__VA_ARGS__)
int main(void)
{
    LOG("hello\n");
    return 0;
}

我們?cè)跇?biāo)識(shí)符 __VA_ARGS__ 前面加上宏連接符 ##,這樣做的好處是,當(dāng)變參列表非空時(shí),## 的作用是連接 fmt,和變參列表,各個(gè)參數(shù)之間用逗號(hào)隔開,宏可以正常使用;當(dāng)變參列表為空時(shí),## 還有一個(gè)特殊的用處,它會(huì)將固定參數(shù) fmt 后面的逗號(hào)刪除掉,這樣宏也就可以正常使用了。

12.3 可變參數(shù)宏的另一種寫法

當(dāng)我們定義一個(gè)變參宏時(shí),除了使用預(yù)定義標(biāo)識(shí)符 __VA_ARGS__ 表示變參列表外,還可以使用下面這種寫法。

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

使用預(yù)定義標(biāo)識(shí)符 VA_ARGS 來(lái)定義一個(gè)變參宏,是 C99 標(biāo)準(zhǔn)規(guī)定的寫法。而上面這種格式是 GNU C 擴(kuò)展的一個(gè)新寫法。我們不再使用VA_ARGS,而是直接使用 args... 來(lái)表示一個(gè)變參列表,然后在后面的宏定義中,直接使用 args 代表變參列表就可以了。

跟上面一樣,為了避免變參列表為空時(shí)的語(yǔ)法錯(cuò)誤,我們也需要添加一個(gè)連接符##。

#define LOG(fmt,args...) printf(fmt,##args)
int main(void)
{
    LOG("hello\n");
    return 0;
}

使用這種方式,你會(huì)發(fā)現(xiàn)這種寫法比使用 __VA_ARGS__ 看起來(lái)更加直觀和方便。

12.4 內(nèi)核中的可變參數(shù)宏

可變參數(shù)宏在內(nèi)核中主要用于日志打印。一些驅(qū)動(dòng)模塊或子系統(tǒng)有時(shí)候會(huì)定義自己的打印宏,可以支持打印開關(guān)、打印格式、優(yōu)先級(jí)控制等。如在 printk.h 頭文件中,我們可以看到 pr_debug 宏的定義。

#if defined(CONFIG_DYNAMIC_DEBUG)
#define pr_debug(fmt, ...) \
    dynamic_pr_debug(fmt, ##__VA_ARGS__)
#elif defined(DEBUG)
#define pr_debug(fmt, ...) \
    printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)
#else
#define pr_debug(fmt, ...) \
    no_printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)
#endif

#define dynamic_pr_debug(fmt, ...)                \
do {                                \
    DEFINE_DYNAMIC_DEBUG_METADATA(descriptor, fmt); \
    if (unlikely(descriptor.flags       \
            & _DPRINTK_FLAGS_PRINT))    \
        __dynamic_pr_debug(&descriptor, pr_fmt(fmt),    \
                   ##__VA_ARGS__);      \
} while (0)

static inline __printf(1, 2)
int no_printk(const char *fmt, ...)
{
    return 0;
}

#define __printf(a, b)    \   
__attribute__((format(printf, a, b)))

看到這個(gè)宏定義,不得不佩服宏的作者。一個(gè)小小的宏,綜合運(yùn)用各種技巧和知識(shí)點(diǎn),把 C 語(yǔ)言發(fā)揮到極致!

這個(gè)宏定義了三個(gè)版本。如果我們?cè)诰幾g內(nèi)核時(shí)有動(dòng)態(tài)調(diào)試選項(xiàng),那么這個(gè)宏就定義為 dynamicprdebug。如果沒(méi)有配置動(dòng)態(tài)調(diào)試選項(xiàng),那我們還可以通過(guò) DEBUG 這個(gè)宏,來(lái)控制這個(gè)宏的打開和關(guān)閉。

no_printk() 作為一個(gè)內(nèi)聯(lián)函數(shù),定義在 printk.h 頭文件中,而且通過(guò) format 屬性聲明,指示編譯器按照 printf 標(biāo)準(zhǔn)去做參數(shù)格式檢查。

最有意思的是 dynamicprdebug 宏,宏定義采用 do{ ... }while(0) 結(jié)構(gòu)。這看起來(lái)貌似有點(diǎn)多余,有它沒(méi)它,我們的宏都可以工作。反正都是執(zhí)行一次,為什么要用這種看似“畫蛇添足”的循環(huán)結(jié)構(gòu)呢?道理很簡(jiǎn)單,這樣定義就是為了防止宏在條件、選擇等分支結(jié)構(gòu)的語(yǔ)句中展開后,產(chǎn)生宏歧義。

比如我們定義一個(gè)宏,由兩條打印語(yǔ)句構(gòu)成。

#define DEBUG() \
 printf("hello ");printf("else\n")

int main(void)
{
    if(1)
        printf("hello if\n");
    else
        DEBUG();
    return 0;
}

程序運(yùn)行結(jié)果如下。

hello if
else

理論情況下,else 分支是執(zhí)行不到的。但通過(guò)運(yùn)行結(jié)果可以看到,程序也執(zhí)行了 else 分支的一部分代碼。這是因?yàn)槲覀兌x的宏由多條語(yǔ)句組成,直接展開后,就變成了下面這樣。

int main(void)
    {
        if(1)
            printf("hello if\n");
        else
            printf("hello ");
            printf("else\n");
        return 0;
    }

多條語(yǔ)句在宏調(diào)用處直接展開,就破壞了程序原來(lái)的 if-else 分支結(jié)構(gòu),導(dǎo)致程序邏輯發(fā)生變化,所以你才會(huì)看到 else 分支的非正常打印。而采用 do{ ... }while(0) 這種結(jié)構(gòu),可以將我們宏定義中的復(fù)合語(yǔ)句包起來(lái),宏展開后,就是一個(gè)代碼塊,就避免了這種邏輯錯(cuò)誤。

一個(gè)小小的宏,暗藏各個(gè)知識(shí)點(diǎn),綜合使用各種技巧,仔細(xì)分析下來(lái),就能學(xué)到很多知識(shí)。大家在以后的工作和學(xué)習(xí)中,可能會(huì)接觸到各種各樣、形形×××的宏,只要我們有牢固的 C 語(yǔ)言基礎(chǔ),熟悉 GNU C 的常用擴(kuò)展語(yǔ)法,再遇到這樣類似的宏,我們都可以慢慢去分析了。不用怕,只有自己真正分析過(guò),才算真正掌握,才能轉(zhuǎn)化為自己的知識(shí)和能力,才能領(lǐng)略它的精妙之處。

本教程根據(jù) C語(yǔ)言嵌入式Linux高級(jí)編程視頻教程 第05期 改編,電子版書籍可加入QQ群:475504428 下載,更多嵌入式視頻教程,可關(guān)注:
微信公眾號(hào):宅學(xué)部落(armlinuxfun)
51CTO學(xué)院-王利濤老師:http://edu.51cto.com/sd/d344f


當(dāng)前題目:嵌入式C語(yǔ)言自我修養(yǎng)12:有一種宏,叫可變參數(shù)宏
文章源于:http://weahome.cn/article/gicsjo.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部