我們在c語言程式設計中會遇到一些引數個數可變的函式,例如printf()
這個函式,它的定義是這樣的:
int printf( const char* format, ...);
它除了有乙個引數format固定以外,後面跟的引數的個數和型別是
可變的,例如我們可以有以下不同的呼叫方法:
printf("%d",i);
printf("%s",s);
printf("the number is %d ,string is:%s", i, s);
究竟如何寫可變引數的c函式以及這些可變引數的函式編譯器是如何實
現的呢?本文就這個問題進行一些**,希望能對大家有些幫助.會c++的
網友知道這些問題在c++裡不存在,因為c++具有多型性.但c++是c的乙個
超集,以下的技術也可以用於c++的程式中.限於本人的水平,文中如果有
不當之處,請大家指正.
(一)寫乙個簡單的可變引數的c函式
下面我們來**如何寫乙個簡單的可變引數的c函式.寫可變引數的
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在這裡是variable-argument(可變引數)的意思.
這些巨集定義在stdarg.h中,所以用到可變引數的程式應該包含這個
標頭檔案.下面我們寫乙個簡單的可變引數的函式,改函式至少有乙個整數
引數,第二個引數也是整數,是可選的.函式只是列印這兩個引數的值.
void ******_va_fun(int i, ...)
我們可以在我們的標頭檔案中這樣宣告我們的函式:
extern void ******_va_fun(int i, ...);
我們在程式中可以這樣呼叫:
******_va_fun(100);
******_va_fun(100,200);
從這個函式的實現可以看到,我們使用可變引數應該有以下步驟:
1)首先在函式裡定義乙個va_list型的變數,這裡是arg_ptr,這個變
量是指向引數的指標.
2)然後用va_start巨集初始化變數arg_ptr,這個巨集的第二個引數是第
乙個可變引數的前乙個引數,是乙個固定的引數.
3)然後用va_arg返回可變的引數,並賦值給整數j. va_arg的第二個
引數是你要返回的引數的型別,這裡是int型.
4)最後用va_end巨集結束可變引數的獲取.然後你就可以在函式裡使
用第二個引數了.如果函式有多個可變引數的,依次呼叫va_arg獲
取各個引數.
如果我們用下面三種方法呼叫的話,都是合法的,但結果卻不一樣:
1)******_va_fun(100);
結果是:100 -123456789(會變的值)
2)******_va_fun(100,200);
結果是:100 200
3)******_va_fun(100,200,300);
結果是:100 200
我們看到第一種呼叫有錯誤,第二種呼叫正確,第三種呼叫儘管結果
正確,但和我們函式最初的設計有衝突.下面一節我們**出現這些結果
的原因和可變引數在編譯器中是如何處理的.
(二)可變引數在編譯器中的處理
我們知道va_start,va_arg,va_end是在stdarg.h中被定義成巨集的,
由於1)硬體平台的不同 2)編譯器的不同,所以定義的巨集也有所不同,下
面以vc++中stdarg.h裡x86平台的巨集定義摘錄如下(』\』號表示折行):
typedef char * va_list;
#define _intsizeof(n) \
((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) )
#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 )
定義_intsizeof(n)主要是為了某些需要記憶體的對齊的系統.c語言的函
數是從右向左壓入堆疊的,圖(1)是函式的引數在堆疊中的分布位置.我
們看到va_list被定義成char*,有一些平台或作業系統定義為void*.再
看va_start的定義,定義為&v+_intsizeof(v),而&v是固定引數在堆疊的
位址,所以我們執行va_start(ap, v)以後,ap指向第乙個可變引數在堆
棧的位址,如圖:
高位址|-----------------------------|
|函式返回位址 |
|-----------------------------|
|....... |
|-----------------------------|
|第n個引數(第乙個可變引數) |
|-----------------------------|<--va_start後ap指向
|第n-1個引數(最後乙個固定引數)|
低位址|-----------------------------|<-- &v
圖( 1 )
然後,我們用va_arg()取得型別t的可變引數值,以上例為int型為例,我
們看一下va_arg取int型的返回值:
j= ( *(int*)((ap += _intsizeof(int))-_intsizeof(int)) );
首先ap+=sizeof(int),已經指向下乙個引數的位址了.然後返回
ap-sizeof(int)的int*指標,這正是第乙個可變引數在堆疊裡的位址
(圖2).然後用*取得這個位址的內容(引數值)賦給j.
高位址|-----------------------------|
|函式返回位址 |
|-----------------------------|
|....... |
|-----------------------------|<--va_arg後ap指向
|第n個引數(第乙個可變引數) |
|-----------------------------|<--va_start後ap指向
|第n-1個引數(最後乙個固定引數)|
低位址|-----------------------------|<-- &v
圖( 2 )
最後要說的是va_end巨集的意思,x86平台定義為ap=(char*)0;使ap不再
指向堆疊,而是跟null一樣.有些直接定義為((void*)0),這樣編譯器不
會為va_end產生**,例如gcc在linux的x86平台就是這樣定義的.
在這裡大家要注意乙個問題:由於引數的位址用於va_start巨集,所
以引數不能宣告為暫存器變數或作為函式或陣列型別.
關於va_start, va_arg, va_end的描述就是這些了,我們要注意的
是不同的作業系統和硬體平台的定義有些不同,但原理卻是相似的.
(三)可變引數在程式設計中要注意的問題
因為va_start, va_arg, va_end等定義成巨集,所以它顯得很愚蠢,
可變引數的型別和個數完全在該函式中由程式**控制,它並不能智慧型
地識別不同引數的個數和型別.
有人會問:那麼printf中不是實現了智慧型識別引數嗎?那是因為函式
printf是從固定引數format字串來分析出引數的型別,再呼叫va_arg
的來獲取可變引數的.也就是說,你想實現智慧型識別可變引數的話是要通
過在自己的程式裡作判斷來實現的.
另外有乙個問題,因為編譯器對可變引數的函式的原型檢查不夠嚴
格,對程式設計查錯不利.如果******_va_fun()改為:
void ******_va_fun(int i, )
可變引數為char*型,當我們忘記用兩個引數來呼叫該函式時,就會出現
core dump(unix) 或者頁面非法的錯誤(window平台).但也有可能不出
錯,但錯誤卻是難以發現,不利於我們寫出高質量的程式.
以下提一下va系列巨集的相容性.
system v unix把va_start定義為只有乙個引數的巨集:
va_start(va_list arg_ptr);
而ansi c則定義為:
va_start(va_list arg_ptr, prev_param);
如果我們要用system v的定義,應該用vararg.h標頭檔案中所定義的
巨集,ansi c的巨集跟system v的巨集是不相容的,我們一般都用ansi c,所以
用ansi c的定義就夠了,也便於程式的移植.
小結:
可變引數的函式原理其實很簡單,而va系列是以巨集定義來定義的,實
現跟堆疊相關.我們寫乙個可變函式的c函式時,有利也有弊,所以在不必
要的場合,我們無需用到可變引數.如果在c++裡,我們應該利用c++的多
態性來實現可變引數的功能,盡量避免用c語言的方式來實現.
C語言中可變引數的用法
c語言中可變引數的用法 我們在c語言程式設計中會遇到一些引數個數可變的函式,例如printf 這個函式,它的定義是這樣的 int printf const char format,它除了有乙個引數format固定以外,後面跟的引數的個數和型別是 可變的,例如我們可以有以下不同的呼叫方法 printf...
C語言中可變引數的用法
在c語言中比較常用的可變引數就是printf 函式原型如下 int printf const char format,寫乙個簡單的可變引數 include void va fun int i,int main void void va fun int i,這樣就實現了乙個簡單的可變引數。其實可變引數...
C語言中可變引數的用法
在c語言中比較常用的可變引數就是printf 函式原型如下 int printf const char format,寫乙個簡單的可變引數 i nclude void va fun int i,int main void void va fun int i,這樣就實現了乙個簡單的可變引數。其實可變引...