一. 何謂可變引數
int printf( const char* format, ...);
這是使用過c語言的人所再熟悉不過的printf函式原型,它的引數中就有固定引數format和可變引數(用」…」表示). 而我們又可以用各種方式來呼叫printf,如:
printf("%d",value);
printf("%s",str);
printf("the number is %d ,string is:%s", value, str);
二.實現原理
c語言用巨集來處理這些可變引數。這些巨集看起來很複雜,其實原理挺簡單,就是根據引數入棧的特點從最靠近第乙個可變引數的固定引數開始,依次獲取每個可變引數的位址。下面我們來分析這些巨集。在vc中的stdarg.h標頭檔案中,針對不同平台有不同的巨集定義,我們選取x86平台下的巨集定義:
typedef char *va_list;
/*把va_list被定義成char*,這是因為在我們目前所用的pc機上,字元指標型別可以用來儲存記憶體單元位址。而在有的機器上va_list是被定義成void*的*/
#define _intsizeof(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
/*_intsizeof(n)巨集是為了考慮那些記憶體位址需要對齊的系統,從巨集的名字來應該是跟sizeof(int)對齊。一般的sizeof(int)=4,也就是引數在記憶體中的位址都為4的倍數。比如,如果sizeof(n)在1-4之間,那麼_intsizeof(n)=4;如果sizeof(n)在5-8之間,那麼_intsizeof(n)=8。*/
#define va_start(ap,v)( ap = (va_list)&v + _intsizeof(v) )
/*va_start的定義為 &v+_intsizeof(v) ,這裡&v是最後乙個固定引數的起始位址,再加上其實際占用大小後,就得到了第乙個可變引數的起始記憶體位址。所以我們執行va_start(ap, v)以後,ap指向第乙個可變引數在的記憶體位址*/
#define va_arg(ap,t) ( *(t *)((ap += _intsizeof(t)) - _intsizeof(t)) )
/*這個巨集做了兩個事情,
①用使用者輸入的型別名對引數位址進行強制型別轉換,得到使用者所需要的值
②計算出本引數的實際大小,將指標調到本引數的結尾,也就是下乙個引數的首位址,以便後續處理。*/
#define va_end(ap) ( ap = (va_list)0 )
/*x86平台定義為ap=(char*)0;使ap不再 指向堆疊,而是跟null一樣.有些直接定義為((void*)0),這樣編譯器不會為va_end產生**,例如gcc在linux的x86平台就是這樣定義的. 在這裡大家要注意乙個問題:由於引數的位址用於va_start巨集,所以引數不能宣告為暫存器變數或作為函式或陣列型別. */
以下再用圖來表示:
在vc等絕大多數c編譯器中,預設情況下,引數進棧的順序是由右向左的,因此,引數進棧以後的記憶體模型如下圖所示:最後乙個固定引數的位址位於第乙個可變引數之下,並且是連續儲存的。
|——————————————————————————|
|最後乙個可變引數 | ->高記憶體位址處
|——————————————————————————|
...................
|——————————————————————————|
|第n個可變引數 | ->va_arg(arg_ptr,int)後arg_ptr所指的地方,
| | 即第n個可變引數的位址。
|——————————————— |
………………………….
|——————————————————————————|
|第乙個可變引數 | ->va_start(arg_ptr,start)後arg_ptr所指的地方
| | 即第乙個可變引數的位址
|——————————————— |
|———————————————————————— ——|
| ||最後乙個固定引數 | -> start的起始位址
|—————————————— —| .................
|—————————————————————————— |
| ||——————————————— |-> 低記憶體位址處
三.printf研究
下面是乙個簡單的printf函式的實現,參考了中的156頁的例子,讀者可以結合書上的**與本文參照。
#include "stdio.h"
#include "stdlib.h"
void myprintf(char* fmt, ...) //乙個簡單的類似於printf的實現,//引數必須都是int 型別
else
parg += sizeof(int); //等價於原來的va_arg
}++fmt;
}while (*fmt != '/0');
parg = null; //等價於va_end
return;
}int main(int argc, char* argv)
在intel+win2k+vc6的機器執行結果如下:
the first test:i=1234
the secend test:i=1234; 0xabcd;j=5678;
四.應用
求最大值:
#include //不定數目引數需要的巨集
int max(int n,int num,...)
va_end(x);//清除變數x
return m;
}main()
c可變引數研究
們知道va start,va arg,va end是在stdarg.h中被定義成巨集的,由於1 硬體平台的不同 2 編譯器的不同,所以定義的巨集也有所不同,下 面以vc 中stdarg.h裡x86平台的巨集定義摘錄如下 號表示折行 typedef char va list define intsiz...
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...