1. 概述
由于在C語言中沒有函數重載,解決不定數目函數參數問題變得比較麻煩;即使采用C++,如果參數個數不能确定,也很難采用函數重載.對這種情況,有些人采用指針參數來解決問題.下面就c語言中處理不定參數數目的問題進行讨論.
2. 定義 大家先看幾宏.
在VC++6.0的include有一個stdarg.h頭檔案,有如下幾個宏定義:
#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 ) // 将指針置為無效
如果對以上幾個宏定義不了解,可以略過,接這看後面的内容.
3. 參數在堆棧中分布,位置 在程序中,堆棧位址是從高到低配置設定的.當執行一個函數的時候,将參數清單入棧,壓入堆棧的高位址部分,然後入棧函數的傳回位址,接着入棧函數的執行代碼,這個入棧過程,堆棧位址不斷遞減,一些黑客就是在堆棧中修改函數傳回位址,執行自己的代碼來達到執行自己插入的代碼段的目的.
總之,函數在堆棧中的分布情況是:位址從高到低,依次是:函數參數清單,函數傳回位址,函數執行代碼段.
堆棧中,各個函數的分布情況是倒序的.即最後一個參數在清單中位址最高部分,第一個參數在清單位址的最低部分.參數在堆棧中的分布情況如下:
最後一個參數
倒數第二個參數
...
第一個參數
函數傳回位址
函數代碼段
測試代碼:
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
const int INT_TYPE = 100000;
const int STR_TYPE = 100001;
const int CHAR_TYPE = 100002;
const int LONG_TYPE = 100003;
const int FLOAT_TYPE = 100004;
const int DOUBLE_TYPE = 100005;
//測試va_start,va_arg的使用方法,函數參數在堆棧中的位址分布情況
void arg_test(int i, ...);
//第一個參數定義可選參數個數,用于循環取初參數内容
void arg_cnt(int cnt, ...);
//第一個參數定義可選參數個數,用于循環取初參數内容
//可變參數采用arg_type,arg_value...的形式傳遞,以處理不同的可變參數類型
void arg_type(int cnt, ...);
int main(int argc,char *argv[])
{
int int_size = _INTSIZEOF(int);
printf("int_size=%d\n", int_size); //int_size=4
arg_test(0, 4);
arg_cnt(4,1,2,3,4);
arg_type(2, INT_TYPE, 222, STR_TYPE, "ok,hello world!");
system("pause");
return 0;
}
//arg_test(0, 4);
void arg_test(int i, ...)
{
int j=0;
va_list arg_ptr;
va_start(arg_ptr, i);
//列印參數i在堆棧中的位址
printf("&i = %p\n", &i); //&i=0012FE88
//列印va_start之後arg_ptr位址,
//應該比參數i的位址高sizeof(int)個位元組
//這時arg_ptr指向下一個參數的位址
printf("arg_ptr = %p\n", arg_ptr); //arg_ptr=0012FE8C
//列印va_arg後arg_ptr的位址
//應該比調用va_arg前高sizeof(int)個位元組
//這時arg_ptr指向下一個參數的位址
j=*((int *)arg_ptr);
printf("%d %d\n", i, j); //0 4
j=va_arg(arg_ptr, int);
printf("arg_ptr = %p\n", arg_ptr); //arg_ptr=0012FE90
va_end(arg_ptr);
printf("%d %d\n", i, j); //0 4
}
//arg_cnt(4,1,2,3,4);
void arg_cnt(int cnt, ...)
{
int value=0;
int i=0;
int arg_cnt=cnt;
va_list arg_ptr;
va_start(arg_ptr, cnt);
for(i = 0; i < cnt; i++)
{
value = va_arg(arg_ptr,int);
printf("value%d=%d\n", i+1, value); //value1=1
//value2=2
//value3=3
//value4=4
}
}
//arg_type(2, INT_TYPE, 222, STR_TYPE, "ok,hello world!");
void arg_type(int cnt, ...)
{
int arg_type=0;
int int_value=0;
int i = 0;
int arg_cnt=cnt;
char *str_value = NULL;
va_list arg_ptr;
va_start(arg_ptr, cnt);
for(i = 0; i < cnt; i++)
{
arg_type = va_arg(arg_ptr,int);
switch(arg_type)
{
case INT_TYPE:
int_value = va_arg(arg_ptr,int);
printf("value%d=%d\n", i+1, int_value); //value1=222
break;
case STR_TYPE:
str_value = va_arg(arg_ptr,char*);
printf("value%d=%d\n", i+1, str_value); //value2=4282180
break;
default:
break;
}
}
}
分析arg_test(0, 4);調用過程,可知函數的參數清單從右向左入棧,棧的狀态如下:

該測試代碼的結果并不是一定的,對了解va_list已經綽綽有餘啦~~
詳細内容也可參見維基百科點選打開連結