在编写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 <stdlib.h>
-
#include <stdio.h>
-
#include <string.h>
-
-
#include <stdarg.h>
-
-
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就能相对简单的解决格式化操作。
阅读(2581) | 评论(0) | 转发(0) |