日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

【嵌入式】C语言高级编程-变参函数(08)

發布時間:2024/4/24 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【嵌入式】C语言高级编程-变参函数(08) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

00. 目錄

文章目錄

    • 00. 目錄
    • 01. format屬性聲明
    • 02. 變參函數的設計思路
    • 03. 變參函數宏
    • 04. 應用示例
    • 05. 附錄

01. format屬性聲明

GNU 通過 attribute 擴展的 format 屬性,用來指定變參函數的參數格式檢查。

用法如下:

__attribute__(( format (archetype, string-index, first-to-check))) void LOG(const char *fmt, ...) __attribute__((format(printf,1,2)));

我們經常實現一些自己的打印調試函數。這些打印函數往往是變參函數,那編譯器編譯程序時,怎么知道我們的參數格式是否正確呢?因為我們實現的是變參函數,參數的個數和格式都不確定。所以編譯器表示壓力很大,不知道該如何處理。

attribute 的format屬性這時候就自帶 BGM,隆重出場了。如上面的示例代碼,我們定義一個 LOG 變參函數,用來實現打印功能。那編譯器編譯程序時,如何檢查我們參數的格式是否正確呢?其實很簡單,通過給 LOG 函數添加 attribute((format(printf,1,2))) 這個屬性聲明,就是告訴編譯器:你知道printf函數不?你怎么對這個函數參數格式檢查的,就按同樣的方法,對 LOG 函數進行檢查。

屬性 format(printf,1,2) 有三個參數。第一個參數 printf 是告訴編譯器,按照 printf 函數的檢查標準來檢查;第2個參數表示在 LOG 函數所有的參數列表中,格式字符串的位置索引;第3個參數是告訴編譯器要檢查的參數的起始位置。

LOG("I am tom\n")LOG("I am tom, I have %d houses!\n",0); LOG("I am tom, I have %d houses! %d cars\n",0,0);

上面代碼,是我們的 LOG 函數使用示例。變參函數,其參數個數跟 printf 函數一樣,是不固定的。那么編譯器如何檢查我們的打印格式是否正確呢?很簡單,我們只需要將格式字符串的位置告訴編譯器就可以了,比如在第2行代碼中:

LOG("I am tom, I have %d houses!\n",0);

在這個 LOG 函數中有2個參數,第一個是格式字符串,第2個是要打印的一個常量值0,用來匹配格式字符串中的格式符。

什么是格式字符串呢?顧名思義,如果一個字符串中含有格式符,那這個字符串就是格式字符串。比如這個格式字符串:“I am tom, I have %d houses!\n”,里面含有格式符%,我們也可以叫它占位符。打印的時候,后面變參的值會代替這個占位符,在屏幕上顯示出來。

我們通過 format(printf,1,2) 屬性聲明,告訴編譯器:LOG 函數的參數,格式字符串的位置在所有參數列表中的索引是1,即第一個參數;要編譯器幫忙檢查的參數,在所有的參數列表里索引是2。知道了 LOG 參數列表中格式字符串的位置和要檢查的參數位置,編譯器就會按照檢查 printf 的格式打印一樣,對 LOG 函數進行參數檢查。

如果我們的 LOG 函數定義為下面形式:

void LOG(int num, char *fmt, ...) __attribute__((format(printf,2,3)));

在這個函數定義中,多了一個參數 num,格式字符串在參數列表中的位置發生了變化(在所有的參數列表中,索引為2),要檢查的第一個變參的位置也發生了變化(索引為3),那我們使用 format 屬性聲明時,就要寫成 format(printf,2,3) 的形式了。

以上就是 format 屬性的使用方法。

02. 變參函數的設計思路

變參函數,顧名思義,跟 printf 函數一樣:參數的個數、類型都不固定。我們在函數體內因為預先不知道傳進來的參數類型和個數,所以實現起來會稍微麻煩一點。首先要解析傳進來的實參,保存起來,然后才能接著像普通函數一樣,對實參進行處理。

我們接下來,就定義一個變參函數,實現的功能很簡單,即打印傳進來的實參值。

程序示例

#include <stdio.h>void fun(int count, ...) {int i = 0;int *args = NULL;args = &count + 1;for (i = 0; i < count; i++){printf("args: %d %p\n", *args, args);args++;} }int main(void) {fun(5, 1, 2, 3, 4, 5);return 0; }

測試結果

# 根據平臺不同,可能結果不同 deng@itcast:~/tmp$ ./a.out args: 832 0x7ffc05619808 args: 832 0x7ffc05619804 args: 832 0x7ffc05619800 args: 21940 0x7ffc056197fc args: 975176187 0x7ffc056197f8

變參函數的參數存儲其實跟 main 函數的參數存儲很像,由一個連續的參數列表組成,列表里存放的是每個參數的地址。在上面的函數中,有一個固定的參數 count,這個固定參數的存儲地址后面,就是一系列參數的指針。在 fun函數中,先獲取 count 參數地址,然后使用 &count + 1 就可以獲取下一個參數的指針地址,使用指針變量 args 保存這個地址,并依次訪問下一個地址,就可以直接打印傳進來的各個實參值了。

上面的程序使用一個 int * 的指針變量依次去訪問實參列表。我們接下來把程序改進一下,使用 char * 類型的指針來實現這個功能,使之兼容更多的參數類型。

程序示例

#include <stdio.h>void fun(int count, ...) {int i = 0;char *args = NULL;args = (void*)&count + 4;for (i = 0; i < count; i++){printf("args: %d %p\n", *(int*)args, args);args += 4;} }int main(void) {fun(5, 1, 2, 3, 4, 5);return 0; }

03. 變參函數宏

對于變參函數,編譯器或計算機系統一般會提供一些宏給程序員使用,用來解析函數的參數。這樣程序員就不用自己解析參數了,直接使用封裝好的宏即可。編譯器提供的宏有:

va_list:定義在編譯器頭文件中 typedef char* va_list;va_start(args,fmt):根據參數 fmt 的地址,獲取 fmt 后面參數的地址,并保存在 args 指針變量中。va_end(args):釋放 args 指針,將其賦值為 NULL。有了這些宏,我們的工作就簡化了很多。我們就不用擼起袖子,自己解析了。

程序示例

#include <stdio.h> #include <stdarg.h>void fun(int count, ...) {va_list args;va_start(args, count);for (int i = 0; i < count; i++){printf("*args = %d\n", va_arg(args, int));}va_end(args); }int main(void) {fun(5, 1, 2, 3, 4, 5);return 0; }

執行結果

deng@itcast:~/tmp$ gcc test.c deng@itcast:~/tmp$ ./a.out *args = 1 *args = 2 *args = 3 *args = 4 *args = 5

我們使用編譯器提供的三個宏,省去了解析參數的麻煩。但打印的時候,我們還必須自己實現。在 V4.0 版本中,我們繼續改進,使用 vprintf 函數實現我們的打印功能。vprintf 函數的聲明在 stdio.h 頭文件中。

# if !(__USE_FORTIFY_LEVEL > 0 && defined __fortify_function) /* Write formatted output to stdout from argument list ARG. */ __STDIO_INLINE int vprintf (const char *__restrict __fmt, __gnuc_va_list __arg) {return vfprintf (stdout, __fmt, __arg); } # endif

vprintf 函數有2個參數,一個是格式字符串指針,一個是變參列表。在下面的程序里,我們可以將,使用 va_start 解析后的變參列表,直接傳遞給 vprintf 函數,實現打印功能。

程序示例

#include <stdio.h> #include <stdarg.h>void fun(char *fmt, ...) {va_list args;va_start(args, fmt);vprintf(fmt, args);va_end(args); }int main(void) {int n = 88;fun("hello world %d\n", n);return 0; }

執行結果

deng@itcast:~/tmp$ ./a.out hello world 88

上一個示例程序基本上實現了跟 printf() 函數相同的功能:支持變參,支持多種格式的數據打印。接下來,我們還需要對其添加 format 屬性聲明,讓編譯器在編譯時,像檢查 printf 一樣,檢查 fun() 函數的參數格式。

程序示例

#include <stdio.h> #include <stdarg.h>void __attribute__((format(printf,1,2))) fun(char *fmt, ...) {va_list args;va_start(args, fmt);vprintf(fmt, args);va_end(args); }int main(void) {int n = 88;fun("hello world %d\n", n);return 0; }

執行結果

deng@itcast:~/tmp$ ./a.out hello world 88

04. 應用示例

在調試一個模塊,或者一個系統,有好多個文件。如果你在每個文件里添加 printf 打印,調試完成后再刪掉,是不是很麻煩?我們自己實現的打印函數,通過一個宏開關,就可以直接關掉或打開,比較方便。比如下面的代碼。

輸出日志信息程序

#include <stdio.h> #include <stdarg.h>#define DEBUGvoid __attribute__((format(printf,1,2))) LOG(char *fmt, ...) { #ifdef DEBUGva_list args;va_start(args, fmt);vprintf(fmt, args);va_end(args); #endif }int main(void) {int n = 88;LOG("hello world %d\n", n);return 0; }

執行結果

deng@itcast:~/tmp$ ./a.out hello world 88 deng@itcast:~/tmp$

當我們定義一個 DEBUG 宏時,LOG 函數實現普通的打印功能;當這個 DEBUG 宏沒有定義,LOG 函數就是個空函數。通過這個宏,我們就實現了打印函數的開關功能,在實際調試中比較實用,非常方便。在 Linux 內核的各個模塊中,你會經常看到大量的自定義打印函數或宏,如 pr_debug、pr_info 等。

除此之外,你可以通過宏,設置一些打印等級。比如可以分為 ERROR、WARNNING、INFO、LOG 等級,根據你設置的打印等級,模塊打印的 log 信息也會不一樣。

05. 附錄

參考:C語言嵌入式Linux高級編程

總結

以上是生活随笔為你收集整理的【嵌入式】C语言高级编程-变参函数(08)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。