可變參數va_list的理解和使用
在編寫C語言代碼的過程中經常會使用printf實現打印的格式化,該函數支持多樣化的參數,同時還支持不定個數的參數,即可變參數。有關可變參數都知道是采用了va_list實現。通常是采用如下的形式:va_list vp;va_start(vp, arg1);typevalue1 = va_arg(vp, typename1);...typevalueN = va_arg(vp, typenameN);va_end(vp);
以上是可變參數的使用方法,其中定義可變參數的函數必須有至少一個確定的參數。比如函數printf的聲明如下所示:int printf(const char *format, ...);
其中format是必須的參數,否則該函數將編譯出錯。從上述的代碼格式中可知,可以通過多次調用va_arg()獲取對應的參數值,該值的獲取還需要知道對應的值類型。多次調用該接口還需要有關可變參數的個數,這些信息都要通過某種形式傳遞到。因此使用va_list必須要知道參數的個數以及對應參數的類型等。
在通常接觸到的可變參數接口printf時實際上傳遞了這些參數,因為調用printf()函數時通常會設定一系列的格式,而這些格式實際上就指出了當前可變參數的個數,以及對應的參數類型,這些都是通過在format中指定的,這樣printf通過format中格式化的符號就能確定類型,同時轉義字符的個數也就確定了可變參數的個數。如下所示:printf("%d,%d,%d,%d\n", a, b , c, d);通過%d的數量可知,可變參數的個數為4個,%d表示整數,因此需要的類型也就對應於int。
這裡簡單的介紹一下va_list的實現方法:在C語言中堆棧是一個非常重要的部分,理解了堆棧的組成才能了解程序的調用過程,這部分簡單的介紹函數調用過程中的堆棧形式。基本模型如下所示:
函數調用的基本模型如下所示,其中參數可以通過當前的棧幀的位置找到對應的參數位置,即根據Calledfunc_EBP的位置就能找到調用當前函數的函數傳遞的參數值。而這些參數值也保存在了堆棧空間中,通過參數的占用空間大小和當前參數的保存位置就能計算出其他參數的位置。由於在va_list中必須有一個已知的確定參數,該參數實際上就是用來確定可變參數的起始位置,確定的基本原理如上圖所示。當然該圖只是一個模型圖,具體的實現可能存在一些差異。這裡不進行詳細的描述。
這裡簡單的編寫一個求可變個數整形求和的方法,其中size參數用來保存參數個數,由於是整形,va_arg直接對int型進行處理。
- #include
- #include
- #include
- #include
- int f(int x, int y, int z)
- {
- return x + y + z;
- }
- int sum(int size, ...)
- {
- va_list vp;
- int s = 0;
- int i = 0;
- va_start(vp, size);
- for (i = 0; i < size; ++ i) {
- s += va_arg(vp, int);
- }
- va_end(vp);
- printf("%d,%d\n", size, s);
- }
- int main()
- {
- int a = 1;
- int b = 2;
- int c = 3;
- printf("%d\n", f(a, b , c));
- sum(5, 20, 30, 40, 50, 20);
- return 0;
- }
有關va_list的用法還很多,在報文解析相關的代碼中存在很多處理,該例子是獲取相同類型的數據,在格式化處理中可能存在各種各種的格式,只需要根據不同的格式分別獲取參數就能完成復雜格式的處理。這部分可以參看redis-cli對cli的處理邏輯,如下所示:
- void *redisCommand(redisContext *c, const char *format, ...) {
- va_list ap;
- void *reply = NULL;
- va_start(ap,format); //已format作為可變參數的相對起始地址
- reply = redisvCommand(c,format,ap);//對ap進行處理
- va_end(ap);
- return reply;
- }
接下來查看ap相關的操作邏輯:
- int redisvFormatCommand(char **target, const char *format, va_list ap) {
- const char *c = format;
- char *cmd = NULL; /* final command */
- int pos; /* position in final command */
- sds curarg, newarg; /* current argument */
- int touched = 0; /* was the current argument touched? */
- char **curargv = NULL, **newargv = NULL;
- int argc = 0;
- int totlen = 0;
- int j;
- /* Abort if there is not target to set */
- if (target == NULL)
- return -1;
- /* Build the command string accordingly to protocol */
- curarg = sdsempty();
- if (curarg == NULL)
- return -1;
- while(*c != '\0') {
- if (*c != '%' || c[1] == '\0') {
- if (*c == ' ') {
- if (touched) {
- newargv = realloc(curargv,sizeof(char*)*(argc+1));
- if (newargv == NULL) goto err;
- curargv = newargv;
- curargv[argc++] = curarg;
- totlen += bulklen(sdslen(curarg));
- /* curarg is put in argv so it can be overwritten. */
- curarg = sdsempty();
- if (curarg == NULL) goto err;
- touched = 0;
- }
- } else {
- newarg = sdscatlen(curarg,c,1);
- if (newarg == NULL) goto err;
- curarg = newarg;
- touched = 1;
- }
- } else {
- char *arg;
- size_t size;
- /* Set newarg so it can be checked even if it is not touched. */
- newarg = curarg;
- switch(c[1]) {
- case 's': //字符串的處理
- arg = va_arg(ap,char*);
- size = strlen(arg);
- if (size > 0)
- newarg = sdscatlen(curarg,arg,size);
- break;
- case 'b':
- arg = va_arg(ap,char*); //先獲取字符串地址
- size = va_arg(ap,size_t); //獲取字符串的長度
- if (size > 0)
- newarg = sdscatlen(curarg,arg,size);
- break;
- case '%':
- newarg = sdscat(curarg,"%");
- break;
- default:
- /* Try to detect printf format */
- {
- static const char intfmts[] = "diouxX";
- char _format[16];
- const char *_p = c+1;
- size_t _l = 0;
- va_list _cpy;
- /* Flags */
- if (*_p != '\0' && *_p == '#') _p++;
- if (*_p != '\0' && *_p == '0') _p++;
- if (*_p != '\0' && *_p == '-') _p++;
- if (*_p != '\0' && *_p == ' ') _p++;
- if (*_p != '\0' && *_p == '+') _p++;
- /* Field width */
- while (*_p != '\0' && isdigit(*_p)) _p++;
- /* Precision */
- if (*_p == '.') {
- _p++;
- while (*_p != '\0' && isdigit(*_p)) _p++;
- }
- /* Copy va_list before consuming with va_arg */
- va_copy(_cpy,ap);
- /* Integer conversion (without modifiers) */
- if (strchr(intfmts,*_p) != NULL) {
- va_arg(ap,int); //獲取參數
- goto fmt_valid;
- }
- /* Double conversion (without modifiers) */
- if (strchr("eEfFgGaA",*_p) != NULL) {
- va_arg(ap,double); //獲取浮點數
- goto fmt_valid;
- }
- /* Size: char */
- if (_p[0] == 'h' && _p[1] == 'h') {
- _p += 2;
- if (*_p != '\0' && strchr(intfmts,*_p) != NULL) {
- va_arg(ap,int); /* char gets promoted to int */
- goto fmt_valid;
- }
- goto fmt_invalid;
- }
- /* Size: short */
- if (_p[0] == 'h') {
- _p += 1;
- if (*_p != '\0' && strchr(intfmts,*_p) != NULL) {
- va_arg(ap,int); /* short gets promoted to int */
- goto fmt_valid;
- }
- goto fmt_invalid;
- }
- /* Size: long long */
- if (_p[0] == 'l' && _p[1] == 'l') {
- _p += 2;
- if (*_p != '\0' && strchr(intfmts,*_p) != NULL) {
- va_arg(ap,long long);
- goto fmt_valid;
- }
- goto fmt_invalid;
- }
- /* Size: long */
- if (_p[0] == 'l') {
- _p += 1;
- if (*_p != '\0' && strchr(intfmts,*_p) != NULL) {
- va_arg(ap,long);
- goto fmt_valid;
- }
- goto fmt_invalid;
- }
- fmt_invalid:
- va_end(_cpy);
- goto err;
- fmt_valid:
- _l = (_p+1)-c;
- if (_l < sizeof(_format)-2) {
- memcpy(_format,c,_l);
- _format[_l] = '\0';
- newarg = sdscatvprintf(curarg,_format,_cpy);
- /* Update current position (note: outer blocks
- * increment c twice so compensate here) */
- c = _p-1;
- }
- va_end(_cpy);
- break;
- }
- }
- if (newarg == NULL) goto err;
- curarg = newarg;
- touched = 1;
- c++;
- }
- c++;
- }
- /* Add the last argument if needed */
- if (touched) {
- newargv = realloc(curargv,sizeof(char*)*(argc+1));
- if (newargv == NULL) goto err;
- curargv = newargv;
- curargv[argc++] = curarg;
- totlen += bulklen(sdslen(curarg));
- } else {
- sdsfree(curarg);
- }
- /* Clear curarg because it was put in curargv or was free'd. */
- curarg = NULL;
- /* Add bytes needed to hold multi bulk count */
- totlen += 1+intlen(argc)+2;
- /* Build the command at protocol level */
- cmd = malloc(totlen+1);
- if (cmd == NULL) goto err;
- pos = sprintf(cmd,"*%d\r\n",argc);
- for (j = 0; j < argc; j++) {
- pos += sprintf(cmd+pos,"$%zu\r\n",sdslen(curargv[j]));
- memcpy(cmd+pos,curargv[j],sdslen(curargv[j]));
- pos += sdslen(curargv[j]);
- sdsfree(curargv[j]);
- cmd[pos++] = '\r';
- cmd[pos++] = '\n';
- }
- assert(pos == totlen);
- cmd[pos] = '\0';
- free(curargv);
- *target = cmd;
- return totlen;
- err:
- while(argc--)
- sdsfree(curargv[argc]);
- free(curargv);
- if (curarg != NULL)
- sdsfree(curarg);
- /* No need to check cmd since it is the last statement that can fail,
- * but do it anyway to be as defensive as possible. */
- if (cmd != NULL)
- free(cmd);
- return -1;
- }
從上述的代碼中實際上是對va_list進行反復的操作獲取具體的內容。通過va_list就能相對簡單的解決格式化操作。