2014年(6)
分类: C/C++
2014-09-11 11:55:26
前段时间思索想写点工厂模式的东东,可中秋假期去爬了趟泰山(顺道逛了
下济南),腿酸肩膀疼的,不想搞些复杂的,恰好节日前有个需求要用到可
变长参数解决...
_CRTIMP int __cdecl printf(const char *, ...);就是的可变长参数的
典型代表。在函数的定义中,最后一个参数设置为...(注:此处不是省略号,
而就是字面上的...),则告诉编译器在函数中要使用可变长的参数,也就是
说,我们可以在定义的函数使用任意多的参数类型。
我们先看看固定参数函数的参数的地址在内存(栈)中的布局情况:
/***************************************************************/
void fun1(int a, int b, int c)
{
printf("&a:%p\n",&a);
printf("&b:%p\n",&b);
printf("&c:%p\n",&c);
}
&a:0018FEF0
&b:0018FEF4
&c:0018FEF8 --ps:运行环境为VC6.0版本
/***************************************************************/
对于32位系统的多数编译器来说,每个栈单元的大小是sizeof(int),而函数
的每一个参数至少需要一个栈单元大小进行存储,从上述的打印可看出,函数
所有参数都是存储在线性连续的栈空间中的,因此可推测可变长参数就是通过
第一个参数向前(暂定为向高地址延伸)或向后(暂定为向低地址延伸)寻址
便可得到所有可变长参数类型及其值。
首先先解决一个问题:
(1)向前还是向后寻址:函数参数是存储在内存中的栈(先进后出)空间的,
而且栈空间是从高地址向低地址方向延伸的(heap堆区正好相反),即可推断
出上述fun函数是参数c先入栈的,然后是参数b入栈,最后是参数a入栈,即参
数是从右到左的顺序依次入栈的。如果知道第一个参数的地址然后向前寻址即
可找到第二个参数的地址(正好符合出栈的顺序),可根据第二个参数找到第
三个参数的地址...
其中printf的声明式前的__cdecl就是告诉编译器函数的参数就是按照从右向左
的顺序入栈的,函数调用者负责清理栈中的参数。
(2)如何获取参数的类型:按照C标准的说明,支持变长参数的函数在原型声明
中,必须有至少一个左固定参数(貌似传统C支持可以不带任何固定参数的纯变长
参数),这个说明也弥补了上面问题的一个遗漏,即没有第一个参数咋办,现给予
正面的回答:必须有至少一个左固定参数。我们可从第一个参数下手,如printf
规定格式%s字符串、%d整形、%f浮点型等等。
(3)取址的偏移量:从上述fun函数来看int为4个字节,其他类型呢?
/***************************************************************/
void
fun2(int a, char b, char* c, double d, float e, short f, long g, int h)
{
printf("&a:%p\n",&a);
printf("&b:%p\n",&b);
printf("&c:%p\n",&c);
printf("&d:%p\n",&d);
printf("&e:%p\n",&e);
printf("&f:%p\n",&f);
printf("&g:%p\n",&g);
printf("&h:%p\n",&h);
}
&a:0018FED8
&b:0018FEDC -int -&b - &a = 4
&c:0018FEE0 -char -&c - &b = 4
&d:0018FEE4 -char* -&d - &c = 4
&e:0018FEEC -double-&e - &d = 8
&f:0018FEF0 -float -&f - &e = 4
&g:0018FEF4 -short -&g - &f = 4
&h:0018FEF8 -long -&h - &g = 4 --ps:运行环境为VC6.0版本
/***************************************************************/
有了以上基础便可写出自己的可变长参数函数了:
/***************************************************************/
#define SIZEOF(n) \
( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
void
fun3(const char* fmt, ...)
{
/*
注此处未使用fmt提供参数型别
*/
char* index = NULL;
index = (char*)&fmt;
printf("1.%s\n", *(char**)index);
index = index + SIZEOF(int);
printf("2.%d\n", *(int*)index);
index = index + SIZEOF(int);
printf("3.%d\n", *(int*)index);
index = index + SIZEOF(char);
printf("4.'%c'\n", *index);
index = index + SIZEOF(double);
printf("5.%.3f\n", *(double*)(index - 4));
index = index + SIZEOF(char*);
printf("6.%s\n", *(char**)index);
}
fun3("hello world", 100, 2, 'a', 40.111, "ni hao");
/***************************************************************/
C标准在stdarg.h中提供了诸多可变长参数的语义定义(具体信息请参照各
编译器的具体实现):
类型va_list:存储参数的类型信息,一般为typedef char * va_list;。
宏va_start(ap, v):初始化参数列表。
宏va_arg(ap, t):返回下一个参数的值。
宏va_end(ap):关闭参数列表(将ap置空)。
具体的使用以在项目中的应用为例:
方式(1):
/***************************************************************/
int __cdecl write_blob(const char* __list, ...)
{
if(NULL == __list)
{
LOG_ERROR("error_0: __list is null\n");
return -1;
}
int nCount = strlen(__list);
if(0 != nCount%2)
{
LOG_ERROR("error_1: strlen(__list)%2 != 0\n");
return -1;
}
va_list index; //(1)定义一个索引(地址)
va_start(index, __list); //(2)初始化参数列表
char* pName = NULL;
SFileUpdate file; //一个数据库操作的结构体
int i = -1;
while(1)
{
i += 2;
if(i > nCount)
break;
if(__list[i-1] != '%')
{
LOG_ERROR("error_2: __list[i-1] != '%'\n");
return -1;
}
/*
楼主项目是为了区分不同的信息类型,以便填充到不同的地方
pName = va_arg(index, int);获取int型
pName = va_arg(index, char*);获取char*型
*/
pName = va_arg(index, char*); //(3)返回下一个参数的值
switch(__list[i])
{
case 't':
file.tableName.assign(pName);
break;
case 'b':
file.lobColumn.assign(pName);
break;
case 'c':
file.commonColumn.assign(pName);
break;
case 'f':
file.AddLobFile(pName);
break;
case 's':
file.AddComVal(pName);
break;
default:
LOG_ERROR("error_3: __list[i] is not in (t,b,c,f,s)\n");
return -1;
break;
}
}
//(4)关闭参数列表(将ap置空)
va_end(index);//必须置空,有的编译器可能在此释放内存空间
ExecuteSql(file);//执行
cout<<"ret:"<
return file.result;
}
/***************************************************************/
方式(2):
/***************************************************************/
int __cdecl write_blob(const char* __list, ...)
{
va_list index;
va_start(index, __list);
int ret = real_write_blob(__list, index);
va_end(index);
return ret;
}
int real_write_blob(char* __list, va_list& index)
{
//char* pName = NULL;
//pName = va_arg(index, int);
...
//return 0;
}
/***************************************************************/
推荐使用方式(2),因为方式(2)更具弹性。