1. 程式人生 > >vsprintf函式和va_list用法詳解

vsprintf函式和va_list用法詳解

void UART1_Printf(char *fmt,...)
{
 va_list ap;
 char string[128];
 va_start(ap, fmt);
 vsprintf(string, fmt, ap);
 UART1_SendString(string);
 va_end(ap);
}

不解。查找了一下用法,記錄如下:

VA_LIST的用法( VA_LIST 是在C語言中解決變參問題的一組巨集):

(1)首先在函式裡定義一具VA_LIST型的變數,這個變數是指向引數的指標
(2)然後用VA_START巨集初始化變數剛定義的VA_LIST變數,這個巨集的第二個
引數是第一個可變引數的前一個引數,是一個固定的引數。(如在執行VA_START
(ap,v)以後,ap指向第一個可變引數在堆疊的地址。)
(3)然後用VA_ARG返回可變的引數,VA_ARG的第二個引數是你要返回的引數的型別。
(4)最後用VA_END巨集結束可變引數的獲取。然後你就可以在函式裡使用第二個引數了。
如果函式有多個可變引數的,依次呼叫VA_ARG獲取各個引數。

函式名: vsprintf 
功 能: 送格式化輸出到串中 
用 法: int vsprintf(char *string, char *format, va_list param); 
程式例:

#include 
#include 
#include

char buffer[80];

int vspf(char *fmt, ...) 

va_list argptr; 
int cnt;

va_start(argptr, fmt); 
cnt = vsprintf(buffer, fmt, argptr); 
va_end(argptr);

return(cnt); 
}

int main(void) 

int inumber = 30; 
float fnumber = 90.0; 
char string[4] = "abc";

vspf("%d %f %s", inumber, fnumber, string); 
printf("%s\n", buffer); 
return 0; 
}

一直沒有搞懂printf函式是怎麼實現的,今天又看了一下可變引數列表的函式的編寫,大概的瞭解了一點。反推出原來沒想到的一個東西,那就是函式形參列表中的變數在記憶體中的位置是順次排列的。
標頭檔案Stdarg.h裡的幾個巨集定義就是利用了這麼一點,順次獲取多個引數,感覺還是挺笨的一個方法。

先看一個最簡單的可變引數列表的函式:
void mytest(int a, ...)
{
     int b;
     va_list ap;
     
     va_start(ap, a);
     b = va_arg(ap, int);
     va_end(ap);
     
     printf("%d, %d", a, b);
}
函式實現列印數字。關鍵問題是搞懂va_list,va_start,va_arg,va_end幾個的意思。
首先是在ADS下寫的程式,開啟stdarg是這個樣子:
typedef    int       *va_list[1];
#define    va_start(ap, parmN)          (void)(*(ap) = __va_start(parmN))
#define    va_arg(ap, type)               __va_arg(*(ap), type)
#define    va_end(ap)                       ((void)(*(ap) = 0))
實在找不到__va_start,__va_arg是怎麼實現的,只好到keil下面去找,這裡的就簡單多了:
typedef   char   *va_list;
#define   va_start(ap,v)                 ap = (va_list)&v + sizeof(v)
#define   va_arg(ap,t)                   (((t *)ap)++[0])
#define   va_end(ap)

原來每個平臺下面的Stdarg標頭檔案的定義都是不相同的。就拿keil那裡的來入手。
1. 首先定義一個va_list型的變數ap,也就是char *。
2. va_start(ap, a)巨集替換之後就是ap = (va_list)&a + sizeof(a);
    首先取a的地址,即第一個固定引數的地址,然後強制型別轉換為va_list,接著後移a的記憶體大小,把當前這個地址值賦給ap。很明顯,就是第一個引數a後面的那個地方,按照上面說的,也就是第一個可變引數。即現在把ap指向第一個可變引數。
3. b = va_arg(ap, int)巨集替換為b = ((int *)ap)++[0];
    自加在後,因此是獲取第一個引數的值賦給b,然後ap後移一個型別的位置,即指向下一個元素的地址。
4. va_end(ap),這裡什麼都沒有做,在ADS那個版本里是將ap指向NULL,防止誤操作。

那麼按照這種做法,當用mytest(100, 200);呼叫這個函式時,它將可以打印出100,200。
如果用mytest(100,200,300);呼叫這個函式,仍然只是顯示100,200。如果要將300打印出來,那麼得再使用一次va_arg巨集,把它賦值給變數c,把c打印出來。

所以stdarg裡的幾個巨集只是機械的把每個引數讀出來,它甚至不知道現在讀的是什麼型別的變數,如果要讀下一個變數的話得往後跳多少位置,這些都只能靠程式自己來判斷指示。

所以printf裡的格式符號"%d,%s,%c"之類的都是人為規定的,得自己程式設計識別這些格式。

相關推薦

vsprintf函式va_list用法

void UART1_Printf(char *fmt,...) {  va_list ap;  char string[128];  va_start(ap, fmt);  vsprintf(string, fmt, ap);  UART1_SendString(st

bowtiebowtie2用法

bowtie 短序列比對工具詳解 常見的短序列比對工具有很多,如fasta、blast、bowtie、shrimp、soap等。每個工具都有其自身的優點,但同時也具備了一些缺點。權衡利弊,我選擇bowtie作為主要的短序列比對工具。它速度很快,比對結果也容易理解。 現在舉個例子來探討bowtie

Python生成器(Generator)yield用法

通過列表生成式,我們可以直接建立一個列表。但是,受到記憶體限制,列表容量肯定是有限的。而且,建立一個包含100萬個元素的列表,不僅佔用很大的儲存空間,如果我們僅僅需要訪問前面幾個元素,那後面絕大多數元素佔用的空間都白白浪費了。 所以,如果列表元素可以按照某種演算法推算出來,那我們是否可以在迴圈的

C++中virtual(虛擬函式)的用法

在面向物件的C++語言中,虛擬函式(virtual function)是一個非常重要的概念。因為它充分體現了面向物件思想中的繼承和多型性這兩大特性,在C++語言裡應用極廣。比如在微軟的MFC類庫中,你會發現很多函式都有virtual關鍵字,也就是說,它們都是虛擬函式。難怪有人甚至稱虛擬函

MultiByteToWideCharWideCharToMultiByte用法

原文連結 注意: 這兩個函式是由Windows提供的轉換函式,不具有通用性 C語言提供的轉換函式為mbstowcs()/wcstombs() 一、函式簡單介紹 涉及到的標頭檔案: 函式所在標頭檔案:windows.h #include <win

Python中sorted()函式的高階用法

sorted()函式的作用是對物件進行排序 函式函式格式: sorted(iterable,key,reverse),key引數可傳入一個自定義函式 下邊通過具體例子說明sorted的具體用法: 一維陣列,直接排序即可: #!/usr/bin/env python # -*-

【Python】pandas軸旋轉stackunstack用法

摘要 前面給大家分享了pandas做資料合併的兩篇[pandas.merge]和[pandas.cancat]的用法。今天這篇主要講的是pandas的DataFrame的軸旋轉操作,stack和unstack的用法。 首先,要知道以下五點: 1.stack:將資料的列“旋轉”為行 2

erlang lists 系列函式功能與用法(共68個函式

  一,帶函式Pred1, all(Pred, List) -> boolean()如果List中的每個元素作為Pred函式的引數執行,結果都返回true,那麼all函式返回true, 否則返回false 例子: lists:all(fun(E) -> true e

traceroutetracert用法

一、什麼是Traceroute?                  Internet,即國際網際網路,是目前世界上最大的計算機網路,更確切地說是網路的網路。它由遍佈全球的幾萬區域網

RxJavaRxAndroid用法

RxJava 在 GitHub 主頁上的自我介紹是 "a library for composing asynchronous and event-based programs using observable sequences for the Java VM"(一個在 J

C++中棧佇列用法

1.C++棧用法詳解 堆疊是一個容器的改編,棧是限定僅在表尾進行插入或刪除操作的線性表,因此表尾端成為棧頂,相應的,表頭端成為棧底,不含有任何元素的棧稱為空棧。它實現了一個先進後出的資料結構(FILO

indexOf,lastIndexOfsubstring 用法

indexOf方法: 返回 String 物件內第一次出現子字串的字元位置。 strObj.indexOf(subString[, startIndex]) 引數 strObj  必選項。String 物件或文字。  subString  必選項。要在 String 物件

c++ 標準模板庫 STL 演算法之 for_each 函式的使用用法

std::for_each template <class InputIterator, class Function> Function for_each (InputIterator first, InputIterator last, Functi

shell 中 if else 用法

基本語法 shell的if語法和C語言等高階語言非常相似,唯一需要注意的地方就是shell的if語句對空格方面的要求比較嚴格(其實shell對所有語法的空格使用都比較嚴格),如果在需要空格的地方沒有打上空格,都會報錯。如if [ $1x == "ip"x ];then ec

C++友元函式友元類用法

在C++中,我們使用類對資料進行了隱藏和封裝,類的資料成員一般都定義為私有成員,成員函式一般都定義為公有的,以此提供類與外界的通訊介面。但是,有時需要定義一些函式,這些函式不是類的一部分,但又需要頻繁地訪問類的資料成員,這時可以將這些函式定義為該函式的友元函式。除了友元函式

SVN trunk(主線) branch(分支) tag(標記) 用法詳細操作步驟

trac load mar span 必須 最可 objc copy 右鍵 原文地址:http://blog.csdn.net/vbirdbest/article/details/51122637 使用場景: 假如你的項目(這裏指的是手機客戶端項目)的某個版本(例如1.0

oracle中的exists not exists 用法

sdn ref 用法詳解 html nbsp e30 .net tail sin oracle中的exists 和not exists 用法詳解 http://blog.csdn.net/zhiweianran/article/details/7868894oracle

js數組中foEachmap的用法 jq中的$.each$.map

cnblogs arr 對象 cal for index source asc 原生js 數組中foEach和map的用法詳解 相同點: 1.都是循環遍歷數組(僅僅是數組)中的每一項。 2.forEach() 和 map() 裏面每一次執行匿名函數都支持3個參數:數組中的

Django基礎(10): URL重定向的HttpResponseDirect, redirectreverse的用法

detail djang 包含 war sed 模型 博客 nbsp rep 利用django開發web應用, 我們經常需要進行URL重定向,有時候還需要給URL傳遞額外的參數。比如用戶添加文章完成後需要轉到文章列表或某篇文章詳情。因此熟練掌握HttpResponseDir

Tag檔案Tag標記的用法

                                      Tag檔案和Tag標記