《C語言介面與實現》作為介面庫,源檔案中大量使用了可變參數表,這些到底是怎麼使用的?先來看這幾個例子,基本明白了可變參數表使用。後面部分從網上整理了原理:
來源程式:
#include <stdio.h>#include <stdarg.h>#include <string.h>//// 使用樣本1:追加串// void Va_Fn1(char *dest, char *data, ...){va_list ap;char *p = data;//指向第一個可變參數//第二個參數就是寫 ... 前面那個va_start(ap, data);//遍曆每一個可變參數,取出來使用while(1){//訪問當前這個可變參數,先用,後遍曆!!strcat(dest, p);p = va_arg(ap, char *);if (p == NULL) break;}//結束va_end(ap);}//// 使用樣本2:累加和// int Va_Fn2(int a, ...){int ret = a;//指向第一個參數int sum = 0;va_list ap;//第二個參數就是寫 ... 前面那個va_start(ap, a);//遍曆每一個可變參數,取出來使用while(1){//先用,後遍曆sum += ret;ret =va_arg(ap, int);if (ret == -1) break;}//結束va_end(ap);return sum;}//// 使用樣本3:使用資料結構// typedef struct{int x;int y;}MY_TYPE;void Va_Fn3(int n, MY_TYPE *p, ...){int i = 0;va_list ap;MY_TYPE *tmp = p;//第二個參數就是寫 ... 前面那個va_start(ap, p);for(; i<n; i++){printf("\t%d-----%d\n", tmp->x, tmp->y);tmp = va_arg(ap, MY_TYPE *);}//結束va_end(ap);}//// 使用樣本4:稍複雜的可變參數表//(char *, int, int),(char *, int, int), ......// void Va_Fn4(char *msg, ...){va_list ap;int i, j;char *str = msg;//指向第一個參數//第二個參數就是寫 ... 前面那個va_start(ap, msg);while(1){//使用可變參數表,先使用i = va_arg(ap, int);j = va_arg(ap, int);printf("\t%s---%d----%d\n", str, i, j);//後遍曆str = va_arg(ap, char *);//第二個參數是【可變參數】的類型if (str == NULL) break;}//結束va_end(ap);}void main(){//樣本1char dest[1000] = {0};Va_Fn1(dest, "Hello ", "OK ", "歡迎 ", "Yes ", NULL);printf("樣本1 = %s\n", dest);//樣本2int x = Va_Fn2(9, 9, 1, 3, 90, -1);printf("樣本2 = %d\n", x);//樣本3MY_TYPE a, b, c;a.x = 100;a.y = 300;b.x = 1100;b.y = 1300;c.x = 6100;c.y = 6300;printf("樣本3:\n");Va_Fn3(3, &a, &b, &c);//樣本4printf("樣本4:\n");Va_Fn4("Hello", 1, 2, "XYZ", 300, 600, "ABC", 77, 88, NULL);}
輸出:
樣本1 = Hello OK 歡迎 Yes樣本2 = 112樣本3: 100-----300 1100-----1300 6100-----6300樣本4: Hello---1----2 XYZ---300----600 ABC---77----88Press any key to continue
原理:
1. 函數參數是以資料結構:棧的形式存取,從右至左入棧
2. 首先是參數的記憶體存放格式:參數存放在記憶體的堆棧段中,在執行函數的時候,從最後一個開始入棧。因此棧底高地址,棧頂低地址,舉個例子如下:
void func(int x, float y, charz);
那麼,調用函數的時候,實參char z 先進棧,然後是 float y,最後是 intx,因此在記憶體中變數的存放次序是 x->y->z,因此,從理論上說,我們只要探測到任意一個變數的地址,並且知道其他變數的類型,通過指標移位元運算,則總可以順藤摸瓜找到其他的輸入變數。<----這就是原理!!
3. 看源碼(vc98/include/stdarg.h):(注意是X86相關的,不是mips,不是ALPHA的,不是PPC等等的!)
typedef char * va_list;
#ifdef _M_IX86
#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 )
#elif defined(_M_MRX000)
這裡有複雜的宏,展開它:在“工程屬性” —〉“C/C++”—〉“Project Options” 手工填入/P,然後rebuild,會產生於.cpp同名的.i檔案,裡面的宏被展開了。來看展開後的第一個函數:
void Va_Fn1(char *dest, char *data, ...){va_list ap;char *p = data;( ap = (va_list)&data + ( (sizeof(data) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) );while(1){strcat(dest, p);p = ( *(char * *)((ap += ( (sizeof(char *) + sizeof(int) - 1) & ~(sizeof(int) - 1) )) - ( (sizeof(char *) + sizeof(int) - 1) & ~(sizeof(int) - 1) )) );if (p == 0) break;}( ap = (va_list)0 );}
再看展開的第二個函數,這個較簡單:
int Va_Fn2(int a, ...){int ret = a;int sum = 0;va_list ap;( ap = (va_list)&a + ( (sizeof(a) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) );while(1){sum += ret;ret =( *(int *)((ap += ( (sizeof(int) + sizeof(int) - 1) & ~(sizeof(int) - 1) )) - ( (sizeof(int) + sizeof(int) - 1) & ~(sizeof(int) - 1) )) );if (ret == -1) break;}( ap = (va_list)0 );return sum;}