`
izuoyan
  • 浏览: 8965191 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

c/c++ 里面的变长参数的实现

 
阅读更多

c里面的变长参数,c++里面也有。提供了:

一个类型

va_list

3个宏

va_start

va_arg

va_end

使用的例子是这样的

int foo(char* fmt, ...){

va_list args;

va_start(args, fmt);

int i = va_arg(args, int);

double f = va_arg(args, double);

va_end(args);

}

使用还是很方便的,但是实现是怎么样的呢? 要讲实现,先得讲讲c里面的函数调用约定。

在x86下,c语言多多种调用约定,而支持变长参数的只有__cdecl。 参数是放在栈上的,调用者负责调整栈.

所以,参数实际上是从第一个到最后一个从低到高排列在栈上的,实现的方法就是

va_list是一个char*.让va_list指向最后一个不是变长参数的参数的后面,也就是第一个变长参数.

va_arg就是按照类型取值,并且把指针往后移动sizeof(type)就行了。

但是到了x64上,就没这么简单了。x64上不论是vc还是gcc都只有一种类似fastcall的调用约定。参数首先是放在寄存器里,不够了再往栈上放。这样一来,参数就不是在栈的内存上连续排列了。变长参数的实现就需要多做点事了。

先讲比较简单的一种,VC的处理方式

vc的调用约定[1] 是,前4个参数,如果是整数,指针或者其他1,2,4,8个字节的参数,放在rcx,rdx,r8,r9里,如果是浮点数,就放在xmm0,xmm1,xmm2,xmm3里,如果大小不满足要求,把参数的指针放在寄存器里。后面的参数,如果大小不符,也是放指针,大小符号,就放值在栈上。强调一下,第一到第四个参数如果是需要放寄存器,一定是放在rcx,rdx,r8,r9或者xmm0,xmm1,xmm2,xmm3。比如,如果第一个参数是int,第二个是double,第三个char*,第四个double,那么就是依次放在rcx,xmm1,r8,xmm3里。

在调用函数之前,需要预留32个字节的寄存器区,这个区域就是用来存放前4个参数的。

在这种调用约定下,实现的va_list的方式倒也简单。如果函数使用了va_start,就会把前4个参数从寄存器里rcx,rdx,r8,r9拷贝到寄存器区,也就跟后面的栈上参数连成一块了。后面就简单了。

但是,如果是浮点数怎么办。这就需要调用者解决了,如果是要对变长参数传参,就需要在把浮点数放到xmm寄存器的同时,给对应的通用寄存器也放一份。这就看出来保证前4个参数和寄存器对应关系的重要性了。

而GCC的调用约定跟VC不同。前6个整数参数会依次放到rdi, rsi, rdx, rcx, r8, r9中,前8个浮点参数放到xmm0到xmm7中。除了使用了更多的寄存器,与vc不同的是,整数和浮点数寄存器是混合使用的不用为没用的参数预留。还是刚才的例子,第一个参数是int,第二个是double,第三个char*,第四个double,参数数会依次放到 rdi,xmm0,rsi,xmm1. 另外,没有在栈上预留寄存器区。 更多的参数和vc一样,放在栈上。

这样一来,变长参数就比较麻烦了。va_list 不再是一个简单的char*了,gcc在x64位下的va_list大概是这样的:

typedef struct {
unsigned int gp_offset;
unsigned int fp_offset;
char *overflow_arg_area;
char *reg_save_area;
} va_list[1];
进入函数的时候,会把6个通用寄存器和8个浮点寄存器连续的拷贝到这个函数的栈帧里。其中一个整数8个字节,一个浮点数16个字节。然后把第一个整数的地址放到reg_save_area里,overflow_arg_area里面是栈上的参数的起始地址。gp_offset设为0,表示整数参数相对reg_save_area的偏移地址,fp_offset设为48,也就是第一个浮点参数的位置。

于是,取参数的时候,整形或者指针类型的,如果gp_offset 不到48 就从reg_save_area + gp_offset 的位置取值,并且把gp_offset加8,否则就从overflow_arg_area取值并且把overflow_arg_area加8

如果是取浮点参数,如果fp_offset 不到 128+48, 就会从reg_save_area + fp_offset的位置取值,并且把fp_offset加16,否则就是从overflow_arg_area取值并且把overflow_arg_area加16.

下面的代码是是一个示意,说明了va_arg的处理过程

#define va_arg(ap, type) /
(*(type*)(__builtin_types_compatible_p(type, long double) /
? (ap->overflow_arg_area += 16, /
ap->overflow_arg_area - 16) /
: __builtin_types_compatible_p(type, double) /
? (ap->fp_offset < 128 + 48 /
? (ap->fp_offset += 16, /
ap->reg_save_area + ap->fp_offset - 16) /
: (ap->overflow_arg_area += 8, /
ap->overflow_arg_area - 8)) /
: (ap->gp_offset < 48 /
? (ap->gp_offset += 8, /
ap->reg_save_area + ap->gp_offset - 8) /
: (ap->overflow_arg_area += 8, /
ap->overflow_arg_area - 8)) /
))

这样的va_list设计会导致一个问题,注意,va_list其实是一个数组,数组在做参数的时候,是转换为指向第一个元素的指针传递的。传递va_list的时候,传进去的是指针,然后在里面改变va_list里面的结构体的值。如果想第二次使用这个va_list,起始就是已经越界了。看下面的例子

void logmessage(const char *fmt, ...)
{
va_list ap;

va_start(ap, fmt);
vfprintf(stdout, fmt, ap);
vfprintf(stderr, fmt, ap);
va_end(ap);
}

int main()
{
logmessage("%s %s %s/n", "a", "b", "c");
return 0;
}

这个代码在gcc x64下会出不可预料的问题,因为第二次再使用ap的时候,ap已经指向了最后的参数的后面了,再调用就会越界,不能访问到正确的参数。

解决的办法就是使用gcc提供的va_copy宏,改成下面这样

void logmessage(const char *fmt, ...)
{
va_list ap;
va_list ap2;

va_start(ap, fmt);
va_copy(ap2, ap);
vfprintf(stdout, fmt, ap);
vfprintf(stderr, fmt, ap2);
va_end(ap);
}

gcc的调用约定可以更充分的使用寄存器,但是给va_list的实现带来了很大的麻烦。

参考文献

[1] "x64 Software Conventions: Calling Conventions" . msdn.microsoft.com. 2010.

分享到:
评论

相关推荐

    c/C++可变参数函数的参数传递机制剖析

    文章对可变参数函数的参数传递机制进行了剖析, 给出了准确、灵活设计可变参数函数的另一种方法

    CC++变长参数用法

    C/C++变长参数用法,记载了C/C++中,变参的用法

    gsoap 2.8 (SOAP/XML 关于C/C++ 语言的自动化实现工具内附 CSharp webservice例子,及GSOAP client和server例子)

    gSOAP编译工具提供了一个SOAP/XML 关于C/C++ 语言的实现,从而让C/C++语言开发web服务或客户端程序的工作变得轻松了很多。绝大多数的C++web服务工具包提供一组API函数类库来处理特定的SOAP数据结构,这样就使得用户...

    C/C++可变参数函数的实现

    一、变长参数函数 头文件:#include 函数声明 int add(int count, ...); 函数定义 int add(int count, ...) { va_list va; va_start(va, count); int sum = 0; for (int i = 0; i &lt; count; i++) sum +...

    C/C++笔试题(附答案,华为面试题系列)

    答:函数和变量被C++编译后在符号库中的名字与C语言的不同,被extern "C"修饰的变 量和函数是按照C语言方式编译和连接的。由于编译后的名字不同,C++程序不能直接调 用C 函数。C++提供了一个C 连接交换指定符号...

    C++中的变长参数深入理解

    变长参数的函数,即参数个数可变、参数类型不定的函数。设计一个参数个数可变、参数类型不定的函数是可能的,最常见的例子是printf函数、scanf函数和高级语言的Format函数。最近的一个项目中就遇到这么一个相关的...

    c语言难点分析整理,C语言

    7. C中可变参数函数实现 38 8. C程序内存中组成部分 41 9. C编程拾粹 42 10. C语言中实现数组的动态增长 44 11. C语言中的位运算 46 12. 浮点数的存储格式: 50 13. 位域 58 14. C语言函数二维数组传递方法 64 15. ...

    免费下载:C语言难点分析整理.doc

    7. C中可变参数函数实现 38 8. C程序内存中组成部分 41 9. C编程拾粹 42 10. C语言中实现数组的动态增长 44 11. C语言中的位运算 46 12. 浮点数的存储格式: 50 13. 位域 58 14. C语言函数二维数组传递方法 64 15. ...

    史上最强的C语言资料

    7. C中可变参数函数实现 38 8. C程序内存中组成部分 41 9. C编程拾粹 42 10. C语言中实现数组的动态增长 44 11. C语言中的位运算 46 12. 浮点数的存储格式: 50 13. 位域 58 14. C语言函数二维数组传递方法 64 15. ...

    高级C语言详解

    7. C中可变参数函数实现 38 8. C程序内存中组成部分 41 9. C编程拾粹 42 10. C语言中实现数组的动态增长 44 11. C语言中的位运算 46 12. 浮点数的存储格式: 50 13. 位域 58 14. C语言函数二维数组传递方法 64 15. ...

    高级C语言 C 语言编程要点

    7. C中可变参数函数实现 38 8. C程序内存中组成部分 41 9. C编程拾粹 42 10. C语言中实现数组的动态增长 44 11. C语言中的位运算 46 12. 浮点数的存储格式: 50 13. 位域 58 14. C语言函数二维数组传递方法 64 15. ...

    C语言难点分析整理

    7. C中可变参数函数实现 38 8. C程序内存中组成部分 41 9. C编程拾粹 42 10. C语言中实现数组的动态增长 44 11. C语言中的位运算 46 12. 浮点数的存储格式: 50 13. 位域 58 14. C语言函数二维数组传递方法 64 15. ...

    高级进阶c语言教程..doc

    7. C中可变参数函数实现 38 8. C程序内存中组成部分 41 9. C编程拾粹 42 10. C语言中实现数组的动态增长 44 11. C语言中的位运算 46 12. 浮点数的存储格式: 50 13. 位域 58 14. C语言函数二维数组传递方法 64 15. ...

    C语言难点分析整理.doc

    7. C中可变参数函数实现 38 8. C程序内存中组成部分 41 9. C编程拾粹 42 10. C语言中实现数组的动态增长 44 11. C语言中的位运算 46 12. 浮点数的存储格式: 50 13. 位域 58 14. C语言函数二维数组传递方法 ...

    tencent-C,C++安全指南.md

    tencent代码安全指南代码规范,C,c++安全指南完整版 # 代码安全指南 面向开发人员梳理的代码安全指南,旨在梳理API层面的风险点并提供详实可行的安全编码方案。 ## 理念 基于DevSecOps理念,我们希望用开发者更易懂...

    cfcc-main.zip

    6.c //实现求二维数组的最大值 7.c //实现猜数 8.c //实现各位相加 9.c //实现求字符个数 10.c //求单词个数 xiao1 11.c //实现密码验证 12.c //实现str算法 13.c //折半法找数 14.c //递归汉诺 15.c //选择法排序 ...

    深入理解C++11:C++11新特性解析与应用

    18第2章 保证稳定性和兼容性 192.1 保持与C99兼容 192.1.1 预定义宏 192.1.2 __func__预定义标识符 202.1.3 _Pragma操作符 222.1.4 变长参数的宏定义以及__VA_ARGS__ 222.1.5 宽窄字符串的连接 232.2 long long整型 ...

    C++网络爬虫项目

    编写单位: 达内IT培训集团 C++教学研发部 编写人员: 闵卫 定稿日期: 2015年11月20日 星期五WEBCRAWLER 网络爬虫实训项目 2 1. 项目概述 互联网产品形形色色,有产品导向的,有营销导向的,也有技术导向的,但是 ...

Global site tag (gtag.js) - Google Analytics