多学多得呀,关于函数的可变参数问题
2011-12-30 14:40
323 查看
C语言中的可变参数是用va_list等几个宏来实现的。其原理就是获取参数进栈的地址,然后分析出各个参数。
这是我查看别人的写东西,下面代码是从别人文章里得到的,在理解下面代码之前,我们先浏览一下下面代码是如何confuse 我们。
VC中IX86平台的:
只区分了sparc平台和其他平台。sparc平台的那个内建函数还没看到源代码,但是看其他平台的代码,没有对齐的考虑。linux下的其他平台为什么没有对齐机制呢?这样的宏定义能很好的工作吗?当出现需要对齐的地方怎么办?
因为测试下面的程序的时候,在我用的linux系统上,我用#include <stdarg.h>;的方法,用gcc的-E参数预编译了后,代码是使用"__builtin_va_alist"的。所以我暂时认定所使用的这个linux是装在sparc平台上的。(后经证实是错误的)
为此,就下面的程序在sparc和x86两平台(32位)上做了测试:
因为cl是32位的编译器,所以默认的是4内存对齐。
用16进制表示x86的内存:
如上图,当考虑内存对齐的时候,取第三个参数的时候,指针的位置是在4的地方,而如果不考虑内存对齐,则是在3的地方。如果在3的地方,则第三个参数就取成0x16000000即是369098752了。
x86平台,win下TC2.0中的结果是:
这是因为TC中把CPU看成16位的,其中int是2个字节,所以是2内存对齐的。
需要注意的是,本文中所说的内存对齐是指栈内存对齐,这和结构体成员的内存对齐是不同的。
/***************************************************************************************************************
*************************************我们先给自己充充电,知识补漏哈**************************************
函数参数的传递原理
函数参数是以数据结构:栈的形式存取,从右至左入栈.eg:
先介绍一下可变参数表的调用形式以及原理:
首先是参数的内存存放格式:参数存放在内存的堆栈段中,在执行函数的时候,从最后一个开始入栈。因此栈底高地址,栈顶低地址,举个例子如下:
void func(int x, float y, char z);
那么,调用函数的时候,实参 char z 先进栈,然后是 float y,最后是 int x,因此在内存中变量的存放次序是 x->y->z,因此,从理论上说,我们只要探测到任意一个变量的地址,并且知道其他变量的类型,通过指针移位运算,则总可以顺藤摸瓜找到其他的输入变量。
***************************************************************************************************************/
以下是更详细的解释:
下面是 <stdarg.h> 里面重要的几个宏定义如下:
typedef char* va_list;
void va_start ( va_list ap, prev_param ); /* ANSI version */
type va_arg ( va_list ap, type );
void va_end ( va_list ap );
va_list 是一个字符指针,可以理解为指向当前参数的一个指针,取参必须通过这个指针进行。
<Step 1> 在调用参数表之前,定义一个 va_list 类型的变量,(假设va_list 类型变量被定义为ap);
<Step 2> 然后应该对ap 进行初始化,让它指向可变参数表里面的第一个参数,这是通过 va_start 来实现的,第一个参数是 ap 本身,第二个参数是在变参表前面紧挨着的一个变量,即“...”之前的那个参数;
<Step 3> 然后是获取参数,调用va_arg,它的第一个参数是ap,第二个参数是要获取的参数的指定类型,然后返回这个指定类型的值,并且把 ap 的位置指向变参表的下一个变量位置;
<Step 4> 获取所有的参数之后,我们有必要将这个 ap 指针关掉,以免发生危险,方法是调用 va_end,他是输入的参数 ap 置为 NULL,应该养成获取完参数表之后关闭指针的习惯。
例如 int max(int n, ...); 其函数内部应该如此实现:
int max(int n, ...) { // 定参 n 表示后面变参数量,定界用,输入时切勿搞错
va_list ap; // 定义一个 va_list 指针来访问参数表
va_start(ap, n); // 初始化 ap,让它指向第一个变参,n之后的参数
int maximum = -0x7FFFFFFF; // 这是一个最小的整数 //经用计算转换十进制为 -2147483647 7F = 127 = 111,1111 留一位表示符号正负
int temp;
for(int i = 0; i < n; i++) {
temp = va_arg(ap, int); // 获取一个 int 型参数,并且 ap 指向下一个参数
if(maximum < temp) maximum = temp;
}
va_end(ap); // 善后工作,关闭 ap
return max;
}
// 在主函数中测试 max 函数的行为(C++ 格式)
int main() {
cout << max(3, 10, 20, 30) << endl;
cout << max(6, 20, 40, 10, 50, 30, 40) << endl;
}
看到这里,建议大家回去再看一次。再回到这里相信大家已会写可变参数函数了。
这是我查看别人的写东西,下面代码是从别人文章里得到的,在理解下面代码之前,我们先浏览一下下面代码是如何confuse 我们。
VC中IX86平台的:
#ifndef _VA_LIST_DEFINED #ifdef _M_ALPHA typedef struct {
char *a0; /* pointer to first homed integer argument */
int offset; /* byte offset of next parameter */
} va_list;
#else
typedef char * va_list;
#endif
#define _VA_LIST_DEFINED
#endif
#ifdef _M_IX86
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )//靠,这里的运算就是为了得到地址
/********************************************************************** ~是位取反的意思。 _INTSIZEOF(n)整个做的事情就是将n的长度化为int长度的整数倍。 比如n为5,二进制就是101b,int长度为4,二进制为100b,那么n化为int长度的整数倍就应该为8。 ~(sizeof(int) – 1) )就应该为~(4-1)=~(00000011b)=11111100b,这样任何数& ~(sizeof(int) – 1) )后最后两位肯定为0,就肯定是4的整数倍了。 (sizeof(n) + sizeof(int) – 1)就是将大于4m但小于等于4(m+1)的数提高到大于等于4(m+1)但小于4(m+2),这样再& ~(sizeof(int) – 1) )后就正好将原长度补齐到4的倍数了。
**********************************************************************/
//下面几个宏参数也没有注释
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_end(ap) ( ap = (va_list)0 )
#elif defined(_M_MRX000)
……
NND
下面是TC2.0下的这几个宏的实现:
#if !defined(__STDARG)
#define __STDARG
typedef void *va_list; #define va_start(ap, parmN) (ap = ...) //TC写法更加变态
#define va_arg(ap, type) (*((type *)(ap))++)
#define va_end(ap)
#define _va_ptr (...)
#endif
/********靠,linux的也不容易理解呀**********/
下面是linux下对这几个宏的实现。很简单,整个文件也就下面这些句:
#ifndef __STDARG_H11
#define __STDARG_H
#ifdef sparc
# define _VA_ALIST_ "__builtin_va_alist"
typedef char *va_list;
# define va_start(ap, p) (ap = (char *) &__builtin_va_alist)
# define va_arg(ap, type) ((type *) __builtin_va_arg_incr((type *) ap))[0]
# define va_end(ap)
#else /* vax, mc68k, 80*86 */
typedef char *va_list;
# define va_start(ap, p) (ap = (char *) (&(p)+1))
# define va_arg(ap, type) ((type *) (ap += sizeof(type)))[-1]
# define va_end(ap)
#endif
#endif /* __STDARG_H */
#if __FIRST_ARG_IN_AX__
#error First arg is in a register, stdarg.h cannot take its address
#endif
只区分了sparc平台和其他平台。sparc平台的那个内建函数还没看到源代码,但是看其他平台的代码,没有对齐的考虑。linux下的其他平台为什么没有对齐机制呢?这样的宏定义能很好的工作吗?当出现需要对齐的地方怎么办?
因为测试下面的程序的时候,在我用的linux系统上,我用#include <stdarg.h>;的方法,用gcc的-E参数预编译了后,代码是使用"__builtin_va_alist"的。所以我暂时认定所使用的这个linux是装在sparc平台上的。(后经证实是错误的)
为此,就下面的程序在sparc和x86两平台(32位)上做了测试:
#include <stdio.h>;#include <stdlib.h>;
#include <stdarg.h>;void tva(int i, ...);
int main(void) {
int i,int1,int2;
char ch;
i = 5;
int1 = 11;
int2 = 22;
ch = 'a';
tva(i, int1, ch, int2);
exit(0); }
void tva(int i, ...) {
int int1;
char ch;
int int2;
va_list ap;
va_start(ap, i);
int1 = va_arg(ap, int);
printf("%d\n", int1);
ch = va_arg(ap, char); // 问题部分。
printf("%c\n", ch);
int2 = va_arg(ap, int);
printf("%d\n", int2);
va_end(ap);
return;
} 在sparc平台上的linux上,用gcc编译的时候会有warning:
test.c: In function `tva':
test.c:35: warning: `char' is promoted to `int' when passed through `...'
test.c:35: warning: (so you should pass `int' not `char' to `va_arg') 运行的时候有段错误,显示:
11
Segmentation fault 只有把问题部分的改成ch = va_arg(ap, int),才完全没有问题。这相当与人为的用参数控制内存对齐了。 另外,在这个linux下,直接使用非sparc平台的那种定义(自己手动定义),则出现了如下面win下模拟那样的内存对齐问题!莫非这就是一台x86的平台,只不过是gcc中的宏定义的有问题了?这个问题先放一边,看来不行的话,得联系一下服务器的管理员了:-( 在x86平台的win上,用VC中的CL编译器,以上的写法完全没有问题,也可改成int,丝毫不受影响。 在x86平台的linux上,没有环境测试。不过模拟了一个(^_^,群众的智慧是无敌的!)。在x86平台的主机上,在win下,用VC中的CL编译器,直接使用linux下的那种宏定义方式(自己定义的)。哈哈,果然有内存对齐的问题! 结果如下:
11
a
369098752
因为cl是32位的编译器,所以默认的是4内存对齐。
用16进制表示x86的内存:
-------------------------------------------------------
| 11 a 22 |
| 0B 00 00 00 61 00 00 00 16 00 00 00 |
| ^ ^ ^ ^ |
| 1 2 4 |
---------------------------------- 9c9c ---------------------
如上图,当考虑内存对齐的时候,取第三个参数的时候,指针的位置是在4的地方,而如果不考虑内存对齐,则是在3的地方。如果在3的地方,则第三个参数就取成0x16000000即是369098752了。
x86平台,win下TC2.0中的结果是:
11
a
5632
这是因为TC中把CPU看成16位的,其中int是2个字节,所以是2内存对齐的。
需要注意的是,本文中所说的内存对齐是指栈内存对齐,这和结构体成员的内存对齐是不同的。
/***************************************************************************************************************
*************************************我们先给自己充充电,知识补漏哈**************************************
函数参数的传递原理
函数参数是以数据结构:栈的形式存取,从右至左入栈.eg:
先介绍一下可变参数表的调用形式以及原理:
首先是参数的内存存放格式:参数存放在内存的堆栈段中,在执行函数的时候,从最后一个开始入栈。因此栈底高地址,栈顶低地址,举个例子如下:
void func(int x, float y, char z);
那么,调用函数的时候,实参 char z 先进栈,然后是 float y,最后是 int x,因此在内存中变量的存放次序是 x->y->z,因此,从理论上说,我们只要探测到任意一个变量的地址,并且知道其他变量的类型,通过指针移位运算,则总可以顺藤摸瓜找到其他的输入变量。
***************************************************************************************************************/
以下是更详细的解释:
下面是 <stdarg.h> 里面重要的几个宏定义如下:
typedef char* va_list;
void va_start ( va_list ap, prev_param ); /* ANSI version */
type va_arg ( va_list ap, type );
void va_end ( va_list ap );
va_list 是一个字符指针,可以理解为指向当前参数的一个指针,取参必须通过这个指针进行。
<Step 1> 在调用参数表之前,定义一个 va_list 类型的变量,(假设va_list 类型变量被定义为ap);
<Step 2> 然后应该对ap 进行初始化,让它指向可变参数表里面的第一个参数,这是通过 va_start 来实现的,第一个参数是 ap 本身,第二个参数是在变参表前面紧挨着的一个变量,即“...”之前的那个参数;
<Step 3> 然后是获取参数,调用va_arg,它的第一个参数是ap,第二个参数是要获取的参数的指定类型,然后返回这个指定类型的值,并且把 ap 的位置指向变参表的下一个变量位置;
<Step 4> 获取所有的参数之后,我们有必要将这个 ap 指针关掉,以免发生危险,方法是调用 va_end,他是输入的参数 ap 置为 NULL,应该养成获取完参数表之后关闭指针的习惯。
例如 int max(int n, ...); 其函数内部应该如此实现:
int max(int n, ...) { // 定参 n 表示后面变参数量,定界用,输入时切勿搞错
va_list ap; // 定义一个 va_list 指针来访问参数表
va_start(ap, n); // 初始化 ap,让它指向第一个变参,n之后的参数
int maximum = -0x7FFFFFFF; // 这是一个最小的整数 //经用计算转换十进制为 -2147483647 7F = 127 = 111,1111 留一位表示符号正负
int temp;
for(int i = 0; i < n; i++) {
temp = va_arg(ap, int); // 获取一个 int 型参数,并且 ap 指向下一个参数
if(maximum < temp) maximum = temp;
}
va_end(ap); // 善后工作,关闭 ap
return max;
}
// 在主函数中测试 max 函数的行为(C++ 格式)
int main() {
cout << max(3, 10, 20, 30) << endl;
cout << max(6, 20, 40, 10, 50, 30, 40) << endl;
}
看到这里,建议大家回去再看一次。再回到这里相信大家已会写可变参数函数了。
相关文章推荐
- 关于可变参数函数的若干问题(转载)
- C#关于静态函数与普通函数之间的调用问题,以及可变参数的使用
- 关于C#函数对象参数传递的问题
- 关于C#函数对象参数传递的问题
- 关于C语言可变参数函数的一些研究和总结
- 关于数组做函数参数的问题汇总
- 关于Java函数传参以及参数在函数内部改变的问题——JAVA值传递与引用最浅显的说明!
- [windows c]关于指针函数和参数可变函数同时应用情况的疑问
- 关于python函数传参数问题 , 不管什么类型都是传引用
- ES3和ES5关于计算顺序的问题(ES5先计算函数ES3则是参数)
- Linux与Windows下可变参数函数问题
- 在调用支持可变参数格式化的函数时的一个很掩蔽问题
- 关于C中函数的可变参数va_list...(转)
- 关于可变参数的函数
- 关于数组做函数参数的问题汇总
- 关于未知dll函数参数的获取问题!
- 有关va_list和vsnprintf输出函数的问题(可变参数)
- 关于二维数组做函数参数,传递形参的形式的问题
- js关于循环中onclick绑定带参数的函数问题
- C语言中的可变参数宏/函数,及可变参数在函数中的传递问题全解析