在C語言中,函數參數的傳遞方式有值傳和址傳.值傳是把實參的一個專用的、臨時的複制值給被調函數中相應的形參被調用函數使用、修改這個傳來的複制值,不會影響實參的值.址傳則是把變量(實參)的位址傳給被調函數.被調函數通過這個位址找到該變量的存放位置,直接對該位址中存放的變量的内容進行存取操作.是以,在被調用函數中可以修改實參的值.這也是函數參數址傳的優點.無論是值傳還是址傳,都要求實參的數目及類型與形參要完全一緻.在一般的程式設計語言中,函數參數的數目及類型是不可變的.即函數被設計之後,隻能接收已固定個數和固定類型的實參.這樣在編譯時,函數形參的存儲空間便于确定.但是在C語言中,不但參數的類型可變,參數的個數也是可變的.也就是說,在形參表中可以不明确指定傳遞參數的個數和類型,一個常見的庫函數Printf() 就是如此.這種函數稱之為可變長參數函數(變參函數).可變長參數函數的參數數目和類型雖然是可變,但其設計原理與固定參數函數的設計原理是一緻的,必須有辦法告訴變參函數沒有指定的參數的個數和類型。下面我們通過對可變長參數函數的了解和設計,在教學中更有助于加深掌握C語言函數設計的思想方法.利用其它語言所不具有的這一可變長參數功能,可以開發靈活、友善、簡潔、功能強的程式子產品.1,可變長參數函數的設計方法
在标準檔案stdarg.h中包含帶參數的宏定義
typedef void *va_list
#define va_arg(ap,type) (*((type *)(ap))++)
#define va_start(ap,lastfix) (ap=…)
#define va_end(ap)
(1) 可變長參數函數用規定格式定義為“類型函數名(firstfix,…,lastfix,…)”.firstfix,…,lastfix表示函數參數清單中的第一個和最後一個固定參數,該參數清單中至少要有一個固定參數,其作用是為了給變參函數确定清單中參數的個數和參數的類型.
(2) 指針類型va_list用來說明一個變量ap(argument pointer——可變參數指針),此變量将依次引用可變參數清單中用省略号“…”代替的每一個參數.即指向将要操作的變參.
(3) 宏va_start (ap,lastfix)是為了初始化變參指針ap,以指向可變參數清單中未命名的第一個參數,即指向lastfix後的第一個變參.它必須在指針使用之前調用一次該宏,參數清單中至少有一個未命名的可變參數.從宏定義可知其正确性.
(4) 宏va_arg (ap,type)調用,将ap指向下一個可變參數,而ap的類型由type确定,type資料類型不使用float類型.調用後将新的變參可指向一個工作變參,如iap=va_start (ap,int)調用.
(5) 宏va_end (ap)從stdarg.h中看出定義為空,即未定義.其功能完成清除變量ap的作用,表明程式以後不再使用,若該指針變量需再使用,必須重新調用宏va_start 以啟動該變量.
2,應用舉例
利用上面讨論的一般可變長參數函數的設計方法,通過執行個體逐漸分析其特點,以加深函數實參與形參一緻性的了解.
2.1 變參類型相同的函數
#include <stdio.h>
#include <stdarg.h>
int mul(int num,int data1,)
{
int total = data1;
int arg,i;
va_list ap;
va_start(ap,data1);
for(i=1;i<num;i++)
{
arg = va_arg(ap,int);
total*=arg;
}
va_end(ap);
return total;
}
long mul2(int i,)
int *p,j;
p = &i+1;//p指向參數清單下一個位置
long s = *p;
for (j=1;j<i;j++)
s *= p[j];
return s;
int main()
printf("%d\n",mul(3,2,3,5));
printf("%d\n",mul2(3,2,3,5));
return 0;
在該例中,for{…}循環中的ap指向的下一個變參類型皆為整型,是以變參類型相同,但變參個數不定.
2.2可變參數在編譯器中的處理
我們知道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指向
圖( 2 )
最後要說的是va_end宏的意思,x86平台定義為ap=(char*)0;使ap不再指向堆棧,而是跟NULL一樣.有些直接定義為((void*)0),這樣編譯器不會為va_end産生代碼,例如gcc在linux的x86平台就是這樣定義的.在這裡大家要注意一個問題:由于參數的位址用于va_start宏,是以參數不能聲明為寄存器變量或作為函數或數組類型.關于va_start, va_arg, va_end的描述就是這些了,我們要注意的是不同的作業系統和硬體平台的定義有些不同,但原理卻是相似的.
#include "stdio.h"
#include "stdlib.h"
void myprintf(char* fmt, ) //一個簡單的類似于printf的實作,//參數必須都是int 類型
//char* pArg=NULL; //等價于原來的va_list
va_list pArg;
char c;
// pArg = (char*) &fmt; //注意不要寫成p = fmt !!因為這裡要對參數取址,而不是取值
// pArg += sizeof(fmt); //等價于原來的va_start
va_start(pArg,fmt);
do
c =*fmt;
if (c != '%')
{
putchar(c); //照原樣輸出字元
}
else
{//按格式字元輸出資料
switch(*++fmt)
{
case 'd':
printf("%d",*((int*)pArg));
break;
case 'x':
printf("%#x",*((int*)pArg));
case 'f':
printf("%f",*((float*)pArg));
default:
}
//pArg += sizeof(int); //等價于原來的va_arg
va_arg(pArg,int);
++fmt;
}while (*fmt != '\0');
//pArg = NULL; //等價于va_end
va_end(pArg);
return;
int main(int argc, char* argv[])
int i = 1234;
int j = 5678;
myprintf("the first test:i=%d",i,j);
myprintf("the secend test:i=%f; %x;j=%d;",i,0xabcd,j);
system("pause");
本文轉自Phinecos(洞庭散人)部落格園部落格,原文連結:http://www.cnblogs.com/phinecos/archive/2007/08/24/868524.html,如需轉載請自行聯系原作者