關於函式的可變引數
前提:一般我們程式設計的時候,函式中形式引數的數目通常是確定的,在呼叫時要依次給出與形式引數對應的實際引數。但在某些情況下我們希望函式的引數個數可以根據需要確定,因此c語言引入可變引數函式。
一、什麼是可變引數
函式中引數的個數和型別是可變的,這就是可變引數
例如:printf()這個函式,它的定義是這樣的:
synopsis
int printf(const char* format,…);
它除了有乙個引數format固定以外,後面跟的引數的個數和型別是可變的,我們經常如下呼叫這個函式:
printf(「%d」,i);
printf(「%s」,s);
printf(「the number is %d,string is: %s」,i ,s);
二、為編寫可變函式做準備
為了編寫可變引數函式,我們通常需要用到stdarg.h>標頭檔案下定義的以下函式:
void va_start(va_list ap,last);//根據初始化last來初始化引數列表。
type va_arg(va_list ap,type);//用於從引數列表中取出乙個引數,引數型別由type指定。
void va_end(va_list ap);//執行清理引數列表的工作。
void va_copy(va_list dest,va_list src);//用於複製引數列表。
其中:
va_list是用於存放引數列表的資料結構。
va是variable-argument(可變引數)的意思。
上述函式通常用巨集來實現,例如標準ansi形式下,這些巨集的定義是:
typedef char * va_list; //字串指標
#define _intsizeof(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )//使用巨集_intsizeof是為了按照整數位元組對齊指標,因為c呼叫協議下面,引數入棧都是整數位元組(指標或者值)。n 是資料型別。
#define va_start(ap,v) ( ap = (va_list)&v + _intsizeof(v) )
#define va_arg(ap,t) ( (t )((ap += _intsizeof(t)) - _intsizeof(t)) )
#define va_end(ap) ( ap = (va_list)0 )
這裡可以man一下檢視英文解釋。
va_list:用來儲存巨集va_start、va_arg和va_end所需資訊的一種型別。為了訪問變長引數列表中的引數,必須宣告va_list型別的乙個物件 。
va_start:訪問變長引數列表中的引數之前使用的巨集,它初始化用va_list宣告的物件,初始化結果供巨集va_arg和va_end使用。
va_arg: 展開成乙個表示式的巨集,該表示式具有變長引數列表中下乙個引數的值和型別。每次呼叫va_arg都會修改用va_list宣告的物件,從而使該物件指向引數列表中的下乙個引數;
va_end:該巨集使程式能夠從變長引數列表用巨集va_start引用的函式中正常返回。
下面我們寫乙個簡單的可變引數的函式
從這個函式的實現可以看到,我們使用可變引數應該有以下步驟:
1)首先在函式裡定義乙個va_list型的變數,這裡是arg_ptr,這個變 量是指向引數的指標.
2)然後用va_start巨集初始化變數arg_ptr,這個巨集的第二個引數是第 乙個可變引數的前乙個引數,是乙個固定的引數.
3)然後用va_arg返回可變的引數,並賦值給整數j. va_arg的第二個 引數是你要返回的引數的型別,這裡是int型.
4)最後用va_end巨集結束可變引數的獲取.然後你就可以在函式裡使 用第二個引數了.如果函式有多個可變引數的,依次呼叫va_arg獲 取各個引數.
三、可變引數在編譯器中的處理
我們知道va_start,va_arg,va_end是在stdarg.h中被定義成巨集的,由於1)硬體平台的不同 2)編譯器的不同,所以定義的巨集也有所不同,
定義_intsizeof(n)主要是為了某些需要記憶體的對齊的系統.c語言的函式是從右向左壓入堆疊的,圖(1)是函式的引數在堆疊中的分布位置.我 們看到va_list被定義成char*,有一些平台或作業系統定義為void*.再看va_start的定義,定義為&v+ _intsizeof(v),而&v是固定引數在堆疊的位址,所以我們執行va_start(ap, v)以後,ap指向第乙個可變引數在堆疊的位址,如圖:
然後,我們用va_arg()取得型別t的可變引數值,以上例為int型為例,我們看一下va_arg取int型的返回值:
j= ( (int)((ap += _intsizeof(int))-_intsizeof(int)) );
首先ap+=sizeof(int),已經指向下乙個引數的位址了.然後返回ap-sizeof(int)的int*指標,這正是第乙個可變引數在堆疊裡的位址(圖2).然後用*取得這個位址的內容(引數值)賦給j.
最後要說的是va_end巨集的意思,x86平台定義為ap=(char*)0;使ap不再指向堆疊,而是跟null一樣.有些直接定義為 ((void*)0),這樣編譯器不會為va_end產生**,例如gcc在linux的x86平台就是這樣定義的.在這裡大家要注意乙個問題:由於引數 的位址用於va_start巨集,所以引數不能宣告為暫存器變數或作為函式或陣列型別.關於va_start, va_arg, va_end的描述就是這些了,我們要注意的是不同的作業系統和硬體平台的定義有些不同,但原理卻是相似的.
關於可變引數的函式
堆疊一般是從右到左的方向,可通過堆疊指標的方式,從堆疊中讀取出呼叫函式中的可變引數。主要涉及到的的系統函式位於 stdarg.h 庫函式中 void va start va list arg ptr,prev param type va arg va list arg ptr,type void v...
可變引數的函式
1 可變引數的概念 c語言中支援引數可變的函式,printf就是乙個典型的引數可變函式,其函式原型如下 include int printf const char format,printf函式的原型中第1個引數format是固定的,後面的引數個數和型別都是可變的。編譯器使用三個點 作為引數的佔位符...
可變引數函式
c函式要在程式中用到以下這些巨集 void va start va list arg ptr,prev param type va arg va list arg ptr,type void va end va list arg ptr va list 用來儲存巨集va start va arg和v...