GNU 通過 attribute 擴展的 format 屬性,用來指定變參函數(shù)的參數(shù)格式檢查。
創(chuàng)新互聯(lián)服務(wù)項目包括保定網(wǎng)站建設(shè)、保定網(wǎng)站制作、保定網(wǎng)頁制作以及保定網(wǎng)絡(luò)營銷策劃等。多年來,我們專注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢、行業(yè)經(jīng)驗、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,保定網(wǎng)站推廣取得了明顯的社會效益與經(jīng)濟效益。目前,我們服務(wù)的客戶以成都為中心已經(jīng)輻射到保定省份的部分城市,未來相信會繼續(xù)擴大服務(wù)區(qū)域并繼續(xù)獲得客戶的支持與信任!它的使用方法如下:
__attribute__(( format (archetype, string-index, first-to-check)))
void LOG(const char *fmt, ...) __attribute__((format(printf,1,2)));
我們經(jīng)常實現(xiàn)一些自己的打印調(diào)試函數(shù)。這些打印函數(shù)往往是變參函數(shù),那編譯器編譯程序時,怎么知道我們的參數(shù)格式對不對呢?因為我們實現(xiàn)的是變參函數(shù),參數(shù)的個數(shù)和格式都不確定。所以編譯器表示壓力很大,不知道該如何處理。
辦法總是有的。這不,attribute 的format屬性這時候就自帶 BGM,隆重出場了。如上面的示例代碼,我們定義一個 LOG 變參函數(shù),用來實現(xiàn)打印功能。那編譯器編譯程序時,如何檢查我們參數(shù)的格式是否正確呢?其實很簡單,通過給 LOG 函數(shù)添加 attribute((format(printf,1,2))) 這個屬性聲明,就是告訴編譯器:你知道printf函數(shù)不?你怎么對這個函數(shù)參數(shù)格式檢查的,就按同樣的方法,對 LOG 函數(shù)進行檢查。
屬性 format(printf,1,2) 有三個參數(shù)。第一個參數(shù) printf 是告訴編譯器,按照 printf 函數(shù)的檢查標準來檢查;第2個參數(shù)表示在 LOG 函數(shù)所有的參數(shù)列表中,格式字符串的位置索引;第3個參數(shù)是告訴編譯器要檢查的參數(shù)的起始位置。是不是沒看明白?舉個例子大家就明白了。
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ù)個數(shù)跟 printf 函數(shù)一樣,是不固定的。那么編譯器如何檢查我們的打印格式是否正確呢?很簡單,我們只需要將格式字符串的位置告訴編譯器就可以了,比如在第2行代碼中:
LOG("I am litao, I have %d houses!\n",0);
在這個 LOG 函數(shù)中有2個參數(shù),第一個是格式字符串,第2個是要打印的一個常量值0,用來匹配格式字符串中的格式符。
什么是格式字符串呢?顧名思義,如果一個字符串中含有格式符,那這個字符串就是格式字符串。比如這個格式字符串:"I am litao, I have %d houses!\n",里面含有格式符%,我們也可以叫它占位符。打印的時候,后面變參的值會代替這個占位符,在屏幕上顯示出來。
我們通過 format(printf,1,2) 屬性聲明,告訴編譯器:LOG 函數(shù)的參數(shù),格式字符串的位置在所有參數(shù)列表中的索引是1,即第一個參數(shù);要編譯器幫忙檢查的參數(shù),在所有的參數(shù)列表里索引是2。知道了 LOG 參數(shù)列表中格式字符串的位置和要檢查的參數(shù)位置,編譯器就會按照檢查 printf 的格式打印一樣,對 LOG 函數(shù)進行參數(shù)檢查。
如果我們的 LOG 函數(shù)定義為下面形式:
void LOG(int num, char *fmt, ...) __attribute__((format(printf,2,3)));
在這個函數(shù)定義中,多了一個參數(shù) num,格式字符串在參數(shù)列表中的位置發(fā)生了變化(在所有的參數(shù)列表中,索引為2),要檢查的第一個變參的位置也發(fā)生了變化(索引為3),那我們使用 format 屬性聲明時,就要寫成 format(printf,2,3) 的形式了。
以上就是 format 屬性的使用方法,鑒于很多同學,可能對變參函數(shù)研究得不多,接下來我們就一起研究下變參函數(shù)的設(shè)計與實現(xiàn),加深對本節(jié)知識的理解。
對于一個普通函數(shù),我們在函數(shù)實現(xiàn)中,不用關(guān)心實參,只需要在函數(shù)體內(nèi)對形參直接引用即可。當函數(shù)調(diào)用時,傳遞的實參和形參個數(shù)和格式是匹配的。
變參函數(shù),顧名思義,跟 printf 函數(shù)一樣:參數(shù)的個數(shù)、類型都不固定。我們在函數(shù)體內(nèi)因為預(yù)先不知道傳進來的參數(shù)類型和個數(shù),所以實現(xiàn)起來會稍微麻煩一點。首先要解析傳進來的實參,保存起來,然后才能接著像普通函數(shù)一樣,對實參進行處理。
我們接下來,就定義一個變參函數(shù),實現(xiàn)的功能很簡單,即打印傳進來的實參值。
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ù)存儲其實跟 main 函數(shù)的參數(shù)存儲很像,由一個連續(xù)的參數(shù)列表組成,列表里存放的是每個參數(shù)的地址。在上面的函數(shù)中,有一個固定的參數(shù) count,這個固定參數(shù)的存儲地址后面,就是一系列參數(shù)的指針。在 print_num 函數(shù)中,先獲取 count 參數(shù)地址,然后使用 &count + 1 就可以獲取下一個參數(shù)的指針地址,使用指針變量 args 保存這個地址,并依次訪問下一個地址,就可以直接打印傳進來的各個實參值了。程序運行結(jié)果如下。
*args:1
*args:2
*args:3
*args:4
*args:5
上面的程序使用一個 int 的指針變量依次去訪問實參列表。我們接下來把程序改進一下,使用 char 類型的指針來實現(xiàn)這個功能,使之兼容更多的參數(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;
}
在這個程序中,我們使用char 類型的指針。涉及到指針運算,一定要注意每一個參數(shù)的地址都是4字節(jié)大小,所以我們獲取下一個參數(shù)的地址是:(char)&count + 4;。不同類型的指針加1操作,轉(zhuǎn)換為實際的數(shù)值運算是不一樣的。對于一個指向 int 類型的指針變量 p,p+1表示 p + 1 sizeof(int),對于一個指向 char 類型的指針變量,p + 1 表示 p + 1 sizeof(char)。兩種不同類型的指針,其運算細節(jié)就體現(xiàn)在這里。當然,程序最后的運行結(jié)果跟上面的程序是一樣的,如下所示。
*args:1
*args:2
*args:3
*args:4
*args:5
對于變參函數(shù),編譯器或計算機系統(tǒng)一般會提供一些宏給程序員使用,用來解析函數(shù)的參數(shù)。這樣程序員就不用自己解析參數(shù)了,直接使用封裝好的宏即可。編譯器提供的宏有:
va_end(args):釋放 args 指針,將其賦值為 NULL。有了這些宏,我們的工作就簡化了很多。我們就不用擼起袖子,自己解析了。
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 版本中,我們使用編譯器提供的三個宏,省去了解析參數(shù)的麻煩。但打印的時候,我們還必須自己實現(xiàn)。在 V4.0 版本中,我們繼續(xù)改進,使用 vprintf 函數(shù)實現(xiàn)我們的打印功能。vprintf 函數(shù)的聲明在 stdio.h 頭文件中。
CRTIMP int __cdecl __MINGW_NOTHROW \
vprintf (const char*, __VALIST);
vprintf 函數(shù)有2個參數(shù),一個是格式字符串指針,一個是變參列表。在下面的程序里,我們可以將,使用 va_start 解析后的變參列表,直接傳遞給 vprintf 函數(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;
}
運行結(jié)果如下。
I am litao, I have 0 car
上面的 my_printf() 函數(shù),基本上實現(xiàn)了跟 printf() 函數(shù)相同的功能:支持變參,支持多種格式的數(shù)據(jù)打印。接下來,我們還需要對其添加 format 屬性聲明,讓編譯器在編譯時,像檢查 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;
}
如果你堅持看到了這里,可能會問,有現(xiàn)成的打印函數(shù)可用,為什么還要費這么大的勁,去實現(xiàn)自己的打印函數(shù)?原因其實很簡單。自己實現(xiàn)的打印函數(shù),除了可以實現(xiàn)自己需要的打印格式,還有2個優(yōu)點,即可以實現(xiàn)打印開關(guān)控制、優(yōu)先級控制。
閉上迷茫的雙眼,好好想象一下。你在調(diào)試一個模塊,或者一個系統(tǒng),有好多個文件。如果你在每個文件里添加 printf 打印,調(diào)試完成后再刪掉,是不是很麻煩?我們自己實現(xiàn)的打印函數(shù),通過一個宏開關(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;
}
當我們定義一個 DEBUG 宏時,LOG 函數(shù)實現(xiàn)普通的打印功能;當這個 DEBUG 宏沒有定義,LOG 函數(shù)就是個空函數(shù)。通過這個宏,我們就實現(xiàn)了打印函數(shù)的開關(guān)功能,在實際調(diào)試中比較實用,非常方便。在 Linux 內(nèi)核的各個模塊中,你會經(jīng)??吹酱罅康淖远x打印函數(shù)或宏,如 pr_debug、pr_info 等。
除此之外,你可以通過宏,設(shè)置一些打印等級。比如可以分為 ERROR、WARNNING、INFO、LOG 等級,根據(jù)你設(shè)置的打印等級,模塊打印的 log 信息也會不一樣。這個功能就不展開了,有興趣你可以試一下。
本教程根據(jù) C語言嵌入式Linux高級編程視頻教程 第05期 改編,電子版書籍可加入QQ群:475504428 下載,更多嵌入式視頻教程,可關(guān)注:
微信公眾號:宅學部落(armlinuxfun)
51CTO學院-王利濤老師:http://edu.51cto.com/sd/d344f
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國服務(wù)器、虛擬主機、免備案服務(wù)器”等云主機租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務(wù)可用性高、性價比高”等特點與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場景需求。