大家熟知的C庫函數printf函數就是一個可變參數函數,它是怎麼實現的呢?不過他實現是有條件的,必須函數參數的入棧順序為從右向左的順序,也即函數的形參,在函數調用之前,必須是最右邊的參數先入棧,並且參數都必須通過棧傳遞,以1個例子說明,如函數func(arg1, arg2,arg3),那麼函數的堆棧應是:
ebp是幀指標寄存器,一般用來存取堆棧,有了堆棧結構,下面我們看看C可變參數的具體實現原理:
#include <stdio.h>enum {ptChar,ptInt,ptFloat,ptDouble,};void printSum(unsigned long paramFormat, ...){/*高16位為可變參數類型,低16位為可變參數個數*/int paramType = (paramFormat >> 16);int paramNum = paramFormat & 0xffff;/*¶mFormat = ebp + 8,第一個參數的地址*/unsigned long *pArg = ¶mFormat;/*ebp + 0x0c, 第二個參數地址*/pArg++;switch(paramType){case ptChar:{int sum = 0;for (int i = 0; i < paramNum; i++){char *pValue = (char *)pArg;sum += *pValue;pArg++;}printf("%d\n", sum);}break;case ptInt:{int sum = 0;for (int i = 0; i < paramNum; i++){int *pValue = (int *)pArg;sum += *pValue;pArg++;}printf("%d\n", sum);}break;case ptFloat:{float sum = 0;/**/pArg++;/*浮點參數,堆棧佔8個位元組,所以指標位移為8*/for (int i = 0; i < paramNum; i++){float *pValue = (float *)pArg;sum += *pValue;pArg++;pArg++;}printf("%f\n", sum);}break;case ptDouble:{double sum = 0;/*雙精確度浮點參數,堆棧佔8個位元組,所以指標位移為8*/for (int i = 0; i < paramNum; i++){double *pValue = (double *)pArg;sum += *pValue;pArg++;pArg++;}printf("%f\n", sum);}break;default:printf("unknowned type!\n");break;}}void main(){unsigned long paramFormat = 3;char a = 1, b = 2, c = 3;printSum(paramFormat, a, b, c);paramFormat = ptInt << 16;paramFormat += 3;int ia = 1, ib = 2, ic = 3;printSum(paramFormat, ia, ib, ic);paramFormat = ptFloat << 16;paramFormat += 3;float fa = 1, fb = 2, fc = 3;printSum(paramFormat, fa, fb, fc);paramFormat = ptDouble << 16;paramFormat += 3;double da = 1, db = 2, dc = 3;printSum(paramFormat, da, db, dc);}
上面這種方法對函數參數的入棧順序有限制,必須從右向左入棧,這就是為什麼pascal調用方式不能實現printf的原因,並且函數形參都要通過棧來傳遞,這對有些編譯器為了最佳化處理,函數參數通過寄存器來傳遞,從而不滿足要求。鑒於次,本文採用C++的預設形參實現可變參數的方法,沒有上面的這些限制,下面是實現代碼:
#include <stdio.h>enum {ptChar,ptInt,ptFloat,ptDouble,};void printSum(unsigned long paramType, void *arg1 = NULL, void *arg2 = NULL, void *arg3 = NULL, void *arg4 = NULL, void *arg5 = NULL, void *arg6 = NULL, void *arg7 = NULL, void *arg8 = NULL, void *arg9 = NULL, void *arg10 = NULL){void *arg[10] = {arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9,arg10,};switch(paramType){case ptChar:{int sum = 0;for (int i = 0; i < 10; i++){if (arg[i] != NULL){char *pValue = (char *)arg[i];sum += *pValue;}elsebreak;}printf("%d\n", sum);}break;case ptInt:{int sum = 0;for (int i = 0; i < 10; i++){if (arg[i] != NULL){int *pValue = (int *)arg[i];sum += *pValue;}elsebreak;}printf("%d\n", sum);}break;case ptFloat:{float sum = 0;for (int i = 0; i < 10; i++){if (arg[i] != NULL){float *pValue = (float *)arg[i];sum += *pValue;}elsebreak;}printf("%f\n", sum);}break;case ptDouble:{double sum = 0;for (int i = 0; i < 10; i++){if (arg[i] != NULL){double *pValue = (double *)arg[i];sum += *pValue;}elsebreak;}printf("%f\n", sum);}break;default:printf("unknowned type!\n");break;}}void main(){unsigned long paramType = ptChar;char a = 1, b = 2, c = 3;printSum(paramType, &a, &b, &c);paramType = ptInt;int ia = 1, ib = 2, ic = 3;printSum(paramType, &ia, &ib, &ic);paramType = ptFloat;float fa = 1, fb = 2, fc = 3;printSum(paramType, &fa, &fb, &fc);paramType = ptDouble;double da = 1, db = 2, dc = 3;printSum(paramType, &da, &db, &dc);}