Chinaunix首页 | 论坛 | 博客
  • 博客访问: 29645
  • 博文数量: 6
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 69
  • 用 户 组: 普通用户
  • 注册时间: 2014-08-26 14:33
文章分类
文章存档

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)更具弹性。

阅读(1768) | 评论(0) | 转发(0) |
0

上一篇:over-eager evaluation超急评估

下一篇:栈溢出

给主人留下些什么吧!~~