GNU 通過 attribute 擴(kuò)展的 format 屬性,用來指定變參函數(shù)的參數(shù)格式檢查。
創(chuàng)新互聯(lián)是網(wǎng)站建設(shè)專家,致力于互聯(lián)網(wǎng)品牌建設(shè)與網(wǎng)絡(luò)營(yíng)銷,專業(yè)領(lǐng)域包括成都網(wǎng)站制作、做網(wǎng)站、電商網(wǎng)站制作開發(fā)、微信小程序定制開發(fā)、微信營(yíng)銷、系統(tǒng)平臺(tái)開發(fā),與其他網(wǎng)站設(shè)計(jì)及系統(tǒng)開發(fā)公司不同,我們的整合解決方案結(jié)合了恒基網(wǎng)絡(luò)品牌建設(shè)經(jīng)驗(yàn)和互聯(lián)網(wǎng)整合營(yíng)銷的理念,并將策略和執(zhí)行緊密結(jié)合,且不斷評(píng)估并優(yōu)化我們的方案,為客戶提供全方位的互聯(lián)網(wǎng)品牌整合方案!
它的使用方法如下:
__attribute__(( format (archetype, string-index, first-to-check)))
void LOG(const char *fmt, ...) __attribute__((format(printf,1,2)));
我們經(jīng)常實(shí)現(xiàn)一些自己的打印調(diào)試函數(shù)。這些打印函數(shù)往往是變參函數(shù),那編譯器編譯程序時(shí),怎么知道我們的參數(shù)格式對(duì)不對(duì)呢?因?yàn)槲覀儗?shí)現(xiàn)的是變參函數(shù),參數(shù)的個(gè)數(shù)和格式都不確定。所以編譯器表示壓力很大,不知道該如何處理。
辦法總是有的。這不,attribute 的format屬性這時(shí)候就自帶 BGM,隆重出場(chǎng)了。如上面的示例代碼,我們定義一個(gè) LOG 變參函數(shù),用來實(shí)現(xiàn)打印功能。那編譯器編譯程序時(shí),如何檢查我們參數(shù)的格式是否正確呢?其實(shí)很簡(jiǎn)單,通過給 LOG 函數(shù)添加 attribute((format(printf,1,2))) 這個(gè)屬性聲明,就是告訴編譯器:你知道printf函數(shù)不?你怎么對(duì)這個(gè)函數(shù)參數(shù)格式檢查的,就按同樣的方法,對(duì) LOG 函數(shù)進(jìn)行檢查。
屬性 format(printf,1,2) 有三個(gè)參數(shù)。第一個(gè)參數(shù) printf 是告訴編譯器,按照 printf 函數(shù)的檢查標(biāo)準(zhǔn)來檢查;第2個(gè)參數(shù)表示在 LOG 函數(shù)所有的參數(shù)列表中,格式字符串的位置索引;第3個(gè)參數(shù)是告訴編譯器要檢查的參數(shù)的起始位置。是不是沒看明白?舉個(gè)例子大家就明白了。
LOG("I am litao\n");
LOG("I am litao, I have %d houses!\n",0);
LOG("I am litao, I have %d houses! %d cars\n",0,0);
上面代碼,是我們的 LOG 函數(shù)使用示例。變參函數(shù),其參數(shù)個(gè)數(shù)跟 printf 函數(shù)一樣,是不固定的。那么編譯器如何檢查我們的打印格式是否正確呢?很簡(jiǎn)單,我們只需要將格式字符串的位置告訴編譯器就可以了,比如在第2行代碼中:
LOG("I am litao, I have %d houses!\n",0);
在這個(gè) LOG 函數(shù)中有2個(gè)參數(shù),第一個(gè)是格式字符串,第2個(gè)是要打印的一個(gè)常量值0,用來匹配格式字符串中的格式符。
什么是格式字符串呢?顧名思義,如果一個(gè)字符串中含有格式符,那這個(gè)字符串就是格式字符串。比如這個(gè)格式字符串:"I am litao, I have %d houses!\n",里面含有格式符%,我們也可以叫它占位符。打印的時(shí)候,后面變參的值會(huì)代替這個(gè)占位符,在屏幕上顯示出來。
我們通過 format(printf,1,2) 屬性聲明,告訴編譯器:LOG 函數(shù)的參數(shù),格式字符串的位置在所有參數(shù)列表中的索引是1,即第一個(gè)參數(shù);要編譯器幫忙檢查的參數(shù),在所有的參數(shù)列表里索引是2。知道了 LOG 參數(shù)列表中格式字符串的位置和要檢查的參數(shù)位置,編譯器就會(huì)按照檢查 printf 的格式打印一樣,對(duì) LOG 函數(shù)進(jìn)行參數(shù)檢查。
如果我們的 LOG 函數(shù)定義為下面形式:
void LOG(int num, char *fmt, ...) __attribute__((format(printf,2,3)));
在這個(gè)函數(shù)定義中,多了一個(gè)參數(shù) num,格式字符串在參數(shù)列表中的位置發(fā)生了變化(在所有的參數(shù)列表中,索引為2),要檢查的第一個(gè)變參的位置也發(fā)生了變化(索引為3),那我們使用 format 屬性聲明時(shí),就要寫成 format(printf,2,3) 的形式了。
以上就是 format 屬性的使用方法,鑒于很多同學(xué),可能對(duì)變參函數(shù)研究得不多,接下來我們就一起研究下變參函數(shù)的設(shè)計(jì)與實(shí)現(xiàn),加深對(duì)本節(jié)知識(shí)的理解。
對(duì)于一個(gè)普通函數(shù),我們?cè)诤瘮?shù)實(shí)現(xiàn)中,不用關(guān)心實(shí)參,只需要在函數(shù)體內(nèi)對(duì)形參直接引用即可。當(dāng)函數(shù)調(diào)用時(shí),傳遞的實(shí)參和形參個(gè)數(shù)和格式是匹配的。
變參函數(shù),顧名思義,跟 printf 函數(shù)一樣:參數(shù)的個(gè)數(shù)、類型都不固定。我們?cè)诤瘮?shù)體內(nèi)因?yàn)轭A(yù)先不知道傳進(jìn)來的參數(shù)類型和個(gè)數(shù),所以實(shí)現(xiàn)起來會(huì)稍微麻煩一點(diǎn)。首先要解析傳進(jìn)來的實(shí)參,保存起來,然后才能接著像普通函數(shù)一樣,對(duì)實(shí)參進(jìn)行處理。
我們接下來,就定義一個(gè)變參函數(shù),實(shí)現(xiàn)的功能很簡(jiǎn)單,即打印傳進(jìn)來的實(shí)參值。
void print_num(int count, ...)
{
int *args;
args = &count + 1;
for( int i = 0; i < count; i++)
{
printf("*args: %d\n", *args);
args++;
}
}
int main(void)
{
print_num(5,1,2,3,4,5);
return 0;
}
變參函數(shù)的參數(shù)存儲(chǔ)其實(shí)跟 main 函數(shù)的參數(shù)存儲(chǔ)很像,由一個(gè)連續(xù)的參數(shù)列表組成,列表里存放的是每個(gè)參數(shù)的地址。在上面的函數(shù)中,有一個(gè)固定的參數(shù) count,這個(gè)固定參數(shù)的存儲(chǔ)地址后面,就是一系列參數(shù)的指針。在 print_num 函數(shù)中,先獲取 count 參數(shù)地址,然后使用 &count + 1 就可以獲取下一個(gè)參數(shù)的指針地址,使用指針變量 args 保存這個(gè)地址,并依次訪問下一個(gè)地址,就可以直接打印傳進(jìn)來的各個(gè)實(shí)參值了。程序運(yùn)行結(jié)果如下。
*args:1
*args:2
*args:3
*args:4
*args:5
上面的程序使用一個(gè) int 的指針變量依次去訪問實(shí)參列表。我們接下來把程序改進(jìn)一下,使用 char 類型的指針來實(shí)現(xiàn)這個(gè)功能,使之兼容更多的參數(shù)類型。
void print_num2(int count,...)
{
char *args;
args = (char *)&count + 4;
for(int i = 0; i < count; i++)
{
printf("*args: %d\n", *(int *)args);
args += 4;
}
}
int main(void)
{
print_num2(5,1,2,3,4,5);
return 0;
}
在這個(gè)程序中,我們使用char 類型的指針。涉及到指針運(yùn)算,一定要注意每一個(gè)參數(shù)的地址都是4字節(jié)大小,所以我們獲取下一個(gè)參數(shù)的地址是:(char )&count + 4;。不同類型的指針加1操作,轉(zhuǎn)換為實(shí)際的數(shù)值運(yùn)算是不一樣的。對(duì)于一個(gè)指向 int 類型的指針變量 p,p+1表示 p + 1 sizeof(int),對(duì)于一個(gè)指向 char 類型的指針變量,p + 1 表示 p + 1 sizeof(char)。兩種不同類型的指針,其運(yùn)算細(xì)節(jié)就體現(xiàn)在這里。當(dāng)然,程序最后的運(yùn)行結(jié)果跟上面的程序是一樣的,如下所示。
*args:1
*args:2
*args:3
*args:4
*args:5
對(duì)于變參函數(shù),編譯器或計(jì)算機(jī)系統(tǒng)一般會(huì)提供一些宏給程序員使用,用來解析函數(shù)的參數(shù)。這樣程序員就不用自己解析參數(shù)了,直接使用封裝好的宏即可。編譯器提供的宏有:
va_end(args):釋放 args 指針,將其賦值為 NULL。有了這些宏,我們的工作就簡(jiǎn)化了很多。我們就不用擼起袖子,自己解析了。
void print_num3(int count,...)
{
va_list args;
va_start(args,count);
for(int i = 0; i < count; i++)
{
printf("args: %d\n", (int *)args);
args += 4;
}
va_end(args);
}
int main(void)
{
print_num3(5,1,2,3,4,5);
return 0;
}
在 V3.0 版本中,我們使用編譯器提供的三個(gè)宏,省去了解析參數(shù)的麻煩。但打印的時(shí)候,我們還必須自己實(shí)現(xiàn)。在 V4.0 版本中,我們繼續(xù)改進(jìn),使用 vprintf 函數(shù)實(shí)現(xiàn)我們的打印功能。vprintf 函數(shù)的聲明在 stdio.h 頭文件中。
CRTIMP int __cdecl __MINGW_NOTHROW \
vprintf (const char*, __VALIST);
vprintf 函數(shù)有2個(gè)參數(shù),一個(gè)是格式字符串指針,一個(gè)是變參列表。在下面的程序里,我們可以將,使用 va_start 解析后的變參列表,直接傳遞給 vprintf 函數(shù),實(shí)現(xiàn)打印功能。
void my_printf(char *fmt,...)
{
va_list args;
va_start(args,fmt);
vprintf(fmt,args);
va_end(args);
}
int main(void)
{
int num = 0;
my_printf("I am litao, I have %d car\n", num);
return 0;
}
運(yùn)行結(jié)果如下。
I am litao, I have 0 car
上面的 my_printf() 函數(shù),基本上實(shí)現(xiàn)了跟 printf() 函數(shù)相同的功能:支持變參,支持多種格式的數(shù)據(jù)打印。接下來,我們還需要對(duì)其添加 format 屬性聲明,讓編譯器在編譯時(shí),像檢查 printf 一樣,檢查 my_printf() 函數(shù)的參數(shù)格式。V5.0 版本如下:
void __attribute__((format(printf,1,2))) my_printf(char *fmt,...)
{
va_list args;
va_start(args,fmt);
vprintf(fmt,args);
va_end(args);
}
int main(void)
{
int num = 0;
my_printf("I am litao, I have %d car\n", num);
return 0;
}
如果你堅(jiān)持看到了這里,可能會(huì)問,有現(xiàn)成的打印函數(shù)可用,為什么還要費(fèi)這么大的勁,去實(shí)現(xiàn)自己的打印函數(shù)?原因其實(shí)很簡(jiǎn)單。自己實(shí)現(xiàn)的打印函數(shù),除了可以實(shí)現(xiàn)自己需要的打印格式,還有2個(gè)優(yōu)點(diǎn),即可以實(shí)現(xiàn)打印開關(guān)控制、優(yōu)先級(jí)控制。
閉上迷茫的雙眼,好好想象一下。你在調(diào)試一個(gè)模塊,或者一個(gè)系統(tǒng),有好多個(gè)文件。如果你在每個(gè)文件里添加 printf 打印,調(diào)試完成后再刪掉,是不是很麻煩?我們自己實(shí)現(xiàn)的打印函數(shù),通過一個(gè)宏開關(guān),就可以直接關(guān)掉或打開,比較方便。比如下面的代碼。
#define DEBUG //打印開關(guān)
void __attribute__((format(printf,1,2))) LOG(char *fmt,...)
{
#ifdef DEBUG
va_list args;
va_start(args,fmt);
vprintf(fmt,args);
va_end(args);
#endif
}
int main(void)
{
int num = 0;
LOG("I am litao, I have %d car\n", num);
return 0;
}
當(dāng)我們定義一個(gè) DEBUG 宏時(shí),LOG 函數(shù)實(shí)現(xiàn)普通的打印功能;當(dāng)這個(gè) DEBUG 宏沒有定義,LOG 函數(shù)就是個(gè)空函數(shù)。通過這個(gè)宏,我們就實(shí)現(xiàn)了打印函數(shù)的開關(guān)功能,在實(shí)際調(diào)試中比較實(shí)用,非常方便。在 Linux 內(nèi)核的各個(gè)模塊中,你會(huì)經(jīng)??吹酱罅康淖远x打印函數(shù)或宏,如 pr_debug、pr_info 等。
除此之外,你可以通過宏,設(shè)置一些打印等級(jí)。比如可以分為 ERROR、WARNNING、INFO、LOG 等級(jí),根據(jù)你設(shè)置的打印等級(jí),模塊打印的 log 信息也會(huì)不一樣。這個(gè)功能就不展開了,有興趣你可以試一下。
本教程根據(jù) C語言嵌入式Linux高級(jí)編程視頻教程 第05期 改編,電子版書籍可加入QQ群:475504428 下載,更多嵌入式視頻教程,可關(guān)注:
微信公眾號(hào):宅學(xué)部落(armlinuxfun)
51CTO學(xué)院-王利濤老師:http://edu.51cto.com/sd/d344f
文章標(biāo)題:嵌入式C語言自我修養(yǎng)08:變參函數(shù)的格式檢查
地址分享:http://www.rwnh.cn/article38/jgpssp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供用戶體驗(yàn)、、小程序開發(fā)、定制網(wǎng)站、微信小程序、關(guān)鍵詞優(yōu)化
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)