們知道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的定義就夠了,也便於程式的移植.
C語言可變引數研究
一 何謂可變引數 int printf const char format,這是使用過c語言的人所再熟悉不過的printf函式原型,它的引數中就有固定引數format和可變引數 用 表示 而我們又可以用各種方式來呼叫printf,如 printf d value printf s str print...
可變引數研究
可變引數的研究,可變引數底層是陣列,確定這個結論之後要實際驗證測試 package day9yue1 public class test1 如果這2個方法都叫test,編譯錯誤,說明引數是一模一樣,沒有過載 結論 string s 和 string.s 結果是一樣的 public void test...
C中的可變引數研究
一 何謂可變引數 int printf const char format,這是使用過c語言的人所再熟悉不過的printf函式原型,它的引數中就有固定引數format和可變引數 用 表示 而我們又可以用各種方式來呼叫printf,如 printf d value printf s str print...