天天看點

va_list詳解

        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詳解

該測試代碼的結果并不是一定的,對了解va_list已經綽綽有餘啦~~

詳細内容也可參見維基百科點選打開連結