这是一个使用可变函数参数的示例程序,如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//#include <Stdarg.h>
typedef char * va_list;
//#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
//#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 )
void va_start (char * var_arg, int n_values)
{
int i = (sizeof(n_values) + sizeof(int) - 1) & ~(sizeof(int) - 1);
var_arg = (char*)&n_values + i;
}
template <class T>
T va_arg(char* var_arg, T arg_kind)
{
int i = (sizeof(T) + sizeof(int) - 1) & ~(sizeof(int) - 1);
return *(T *)((var_arg += i) - i);
}
void va_end(char* var_arg)
{
var_arg = (char*)0;
}
float
average( int n_values, ... )
{
char * var_arg;
int count;
float sum = 0;
int int_kind;
/*
** Prepare to access the variable arguments.
*/
va_start ( var_arg, n_values );
/*
** Add the values from the variable argument list.
*/
for( count = 0; count < n_values; count += 1 )
{
sum += va_arg( var_arg, int_kind );
}
/*
** Done processing variable arguments.
*/
va_end( var_arg );
return sum / n_values;
}
void main( void )
{
float a = average(2, 1, 3);
printf("a=%f/n", a);
return ;
}
我们知道使用可变参数的函数需要包含Stdarg.h文件,在X86平台下主要是使用了Stdarg.h文件的如下部分(将它们直接拷贝到程序中可以省略对Stdarg.h文件的包含):
typedef char * va_list;
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
#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 )
为了理解这些宏定义,我在示例程序中将它们注释掉,并且添加了三个函数(这样可以进行跟踪调试,宏定义是不行的):
void va_start (char * var_arg, int n_values);
T va_arg(char* var_arg, T arg_kind);
void va_end(char* var_arg);
这个示例程序能够编译通过,但是运行时发生错误。
那位帮我看看到底是什么原因?或者帮我解释一下那些宏定义,谢谢了!
可变参哦,呵呵!
va_start 指定第一个参数的位置
va_arg获得参数的值(需要指定类型)
va_end结束可变参数的获取
可变参数
像printf 输出函数是可变参函数一样,在函数实现里面会用到
va_start 指定第一个参数的位置
va_arg获得参数的值(需要指定类型)
va_end结束可变参数的获取
va_start 指定第一个参数的位置
va_arg获得参数的值(需要指定类型)
va_end结束可变参数的获取
谢谢楼上的,这些我都知道。我想问的是:
( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
比如这句话是一个什么意思?这样当出现类似的问题而又没有现成的解决方法(前人没有做过类似的工作)的时候,自己可以找到解决的办法。同时通过解析c的源码,达到向大师们学习的目的。
-
C/C++ code
-
楼上,请问如何看出是32位对齐?
在Stdarg.h中的注释说:
要注意char,short隐式上升为int型;float隐式上升为double型。
((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int)-1))
在本例程中表示32位对齐,但也可以支持64位对齐。
我想知道这句话为什么实现了数据对齐的功能(看不懂哇,想将宏转换为函数调试一下,还发生了异常),谢谢:-)
guanzhu xuexi
你自己随便找个数字,然后拿笔计算一下就知道了,这种对齐方法在一些原代码中常见。
呵呵,等我给楼主一个详细的解释吧。顺便给我的变参笔记一个完美的结尾:-)
所谓变参,是被调函数直接操作栈空间的一种应用。
原本的操作宏,不改变栈空间。
你定义的函数,修改了栈空间。
这就是你改的“函数”方式的变参操作不能用的原因。
-
C/C++ code
-
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
#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 )
仔细分析这段定义,你会发现:
⒈ 对 _INTSIZEOF(n) :
在 32 位系统(32位编译器)上,sizeof(int) 尺寸为 4 ,该宏计算出 4 字节对齐 后存储 n 的数据所需字节数。
在其他系统(相应的编译器)上类似。你可以自己分析。
⒉ 对 va_start (ap,v) :
考虑 v 是第一个函数参数。ap 得到的是(对齐后) v 后面的相邻参数的地址。
实际上也就是将“第一个可变的参数”地址存进 ap 。
⒊ 对 va_arg(ap,t) :
先 += 对齐后 t 类型数据所需字节数,再 - 该字节数,再转换成指针取值。
换句话说,返回 *(t*)ap ,再修改 ap ,增量为 t 类型的数据尺寸。
这时, ap 就是“下一个可变参数”地址。
⒋ va_end 类似。你可以自己分析。
对于函数方式的实现。
你可以考虑将参数传递改成引用的方式。
在此基础上,再把变参逻辑实现进去。
goodluck
字节对齐 的学习笔记
一、问题的提出
两年之前我写过一篇可变参数学习笔记,里面曾经简单的解释过一句:
代码
((sizeof(n)+sizeof(int)-1)&~(sizeof(int)-1))
的作用是在考虑字节对齐 的因素下计算第一个可变参数的起始地址。
当时限于时间和水平,未能做更详细的解释。
今天(2007-11-26)在csdn论坛上看到了一个帖子
http://topic.csdn.net/u/20071123/16/c8d17d3f-9f49-49af-a6d8-1d7a7d84dc1c.html?seed=303711257
问题:CRT源码分析中一个关于可变函数参数的问题
提问者:Sun_Moon_Stars
里面又问到了这个宏,于是决定抽出半天时间,把这个问题详细的说清楚。也算是把我的那篇文章
做一个完美的结尾。
二、引子
先看一个日常生活中的问题,
问题1:假设有要把一批货物放到集装箱里,货物有12件,
一个箱子最多能装6件货物,求箱子的数目。
解答:显然我们需要12/6=2个箱子,并且每个箱子都是满的。这个连小学生都会算:-)
问题2: 把问题1的条件改一下,假设一个箱子最多能装5件货物,那么现在的箱子数是多少?
解答: 12/5=2.4个,但是根据实际情况,箱子的个数必须为整数,(有不知道这个常识的就不要再往下看了,
回小学重读吧,呵呵)自然我们就要取3,
下面把问题一般化
三、一般数学模型
问题3:设一个箱子最多可以装M件货物,且现有N件货物,
则至少需要多少个箱子,给出一般的计算公式。
这里要注意两点
1、箱子的总数必须为整数
2、N不一定大于M,很显然,即使N <M,也得需要一只箱子
四、通项公式
1、预备知识
在讨论之问题3的解答之前,我们先明确一下/运算符的含义。
定义/运算为取整运算,即
对任意两个整数N,M,必然有且只有唯一的整数X,满足
X*M <= N < (X+1)*M,那么记N/M=X。
这个也正是c里/运算的确切含义。x的存在性和唯一性的严格证明可以见数论教材。
以后如无额外说明,/运算的含义均和本处一致。
/运算有一个基本的性质
若N=MX+Y,则N/M=X+Y/M,证明略
注意:N不是可以随便拆的,设N=A+B,那么一般情况下N/M 不一定等于 A/M+B/M,
A和B至少有一个是M的倍数才行。
2、分步讨论
根据上面的/运算符的定义,我们可以得到问题三的解答,分情况讨论一下
已知N/M=X,那么当
(1)、当N正好是M的倍数时即N=M*X时,那么箱子数就是X=N/M
(2)、如果N不是M的倍数,即N=M*X+Y(1 <=Y <M)时
那么显然还要多一个箱子来装余下的Y件货物,
则箱子总数为X+1 = N/M+1
3、一般公式
上面的解答虽然完整,但是用起来并不方便,因为每次都要去判断N和M的倍数关系,
我们自然就要想一个统一的公式,于是,下面的公式出现了
箱子数目为 (N+M-1)/M
这个式子用具体数字去验证是很简单的,留给读者去做。
我这里给一个完整的数学推导:
现在已经假定 /运算的结果为取整(或者说取模),即
N/M=X,则XM <=N <(X+1)M
那么,
(1)、当N=MX时,(N+M-1)/M= MX/M+(M-1)/M=X
(2)、当N=MX+Y(1 <=Y <M)时,
由1 <=Y < M,同时加上M-1,得到M <= Y-1+M <= 2M-1 <2M
根据 /运算的定义 (Y-1+M) /M = 1
所以 (N+M-1)/M = (MX+Y+M-1)/M= MX/M+(Y+M-1)/M= X+1
显然 公式 (N+M-1)/M与2中的分步讨论结果一致。
可能有的读者还会问,这个公式是怎么想出来的,怎么就想到了加上那个M-1?
这个问题可以先去看看数论中的余数理论。
五、对齐代码的分析
有了上面的数学基础,我们再来看看开头所说的对齐代码的含义
((sizeof(n)+sizeof(int)-1)&~(sizeof(int)-1))
意义就很明显了
这里。机器字长度sizeof(int)相当于箱子的容量M,变量的真实字节大小相于
货物总数N,整个代码就是求n所占的机器字数目。
顺便仔细的解释一下
~(sizeof(int)-1))
这里用到了一个位运算的技巧,即若M是2的幂,那么
N/M = N &(~(M-1)),
这个读者可以用具体的数自己验证这个结论。
这里给出数学的解释:
设N,M都是二进制数字,且M为2的幂,即M=power(2,Y);
那么必有
N=M*X+Z(1 < =Y < M)
而注意到这里的N,M,Z都是二进制表示,所以把N的最右边的Y位数字就是余数Z.
剩下的左边数字就是模X.我们的任务就是把左边的模求出来就可以。
注意:
(1)这里最关键的一点就是M必须是2的幂(有人常常理解成2的倍数也可以,那是不对的),
否则上面的结论是不成立的
(2) ~(M-1)更专业的叫法就是掩码(mask)。因为数字和这个掩码进行与运算后,数字的最右边Y位的
数字被置0("掩抹"掉了).即掩码最右边的0有多少位,数字最右边就有多少位被清0。
小结:
1、字节对齐 的数学本质就是数论中的取模运算。在计算机上的含义就是求出一个对象占用的机器字数目。
2、在c中/运算可以用位运算和掩码来实现以加快速度,前提是机器字长度必须为2的幂。
ls是个有爱心的大叔,pfpf
好贴
我将你的程序稍加改动,可以实现变参的要求.
你主要的问题就是为了和标准宏调用方式一致而采用了传值的方式,而这样一来,变量在栈中的存储方式已经不是你想象的那样了.
我下面的程序就是在你的基础之上将值传递改为了地址传递,我试过了,执行起来应该没有什么问题,你可以参考一下,呵呵
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef char *va_list;
float average( int &n_values, ... );
void va_start ( char **var_arg, int &n_values );
template < class T > T va_arg( char **var_arg, T arg_kind );
void va_end( char **var_arg );
int main( void )
{
int nCount = 2;
int nTemp1 = 1;
int nTemp2 = 300;
float fAverage = average( nCount, nTemp1, nTemp2 );
printf( "average = %f/n", fAverage );
return 0;
}
float average( int &n_values, ... )
{
char *var_arg;
int count;
float sum = 0;
int int_kind;
// Prepare to access the variable arguments.
va_start ( &var_arg, n_values );
// Add the values from the variable argument list.
for( count = 0; count < n_values; count += 1 )
{
sum += va_arg( &var_arg, int_kind );
}
// Done processing variable arguments.
va_end( &var_arg );
return sum / n_values;
}
void va_start ( char **var_arg, int &n_values )
{
int i = ( sizeof(n_values) + sizeof(int) - 1 ) & ~( sizeof(int) - 1 );
*var_arg = ( char* )&n_values - i;
}
template < class T >
T va_arg( char **var_arg, T arg_kind )
{
int i = ( sizeof(T) + sizeof(int) - 1 ) & ~( sizeof(int) - 1 );
return *( T * )(( *var_arg -= i ) + i );
}
void va_end( char **var_arg )
{
*var_arg = ( char* )0;
}
对了
template < class T >
T va_arg( char **var_arg, T arg_kind )
{
int i = ( sizeof(T) + sizeof(int) - 1 ) & ~( sizeof(int) - 1 );
return *( T * )(( *var_arg -= i ) + i );
}
里面的 *var_arg -= i ,需要说明一下:
因为我所用的环境存储变量的方法是,对于先定义的变量存在栈的下部,后定义的放在栈的上部,因为nCount是最后定义的,nTemp1,nTemp2都在它上面,所以用的是-=,而不是你用的+=.
呵呵,还有关于int i = ( sizeof(n_values) + sizeof(int) - 1 ) & ~( sizeof(int) - 1 ); 的分析,
看了楼上各位大虾的分析,我也来说一下我自己的看法:
首先,变参函数的标准宏定义是直接对栈进行操作,参考如下代码
double sum(int lim,...)
{
va_list ap; // declare object to hold arguments
double tot = 0;
int i;
va_start (ap, lim); // initialize ap to argument list
for (i = 0; i < lim; i++)
tot += va_arg(ap, double); // access each item in argument list
va_end(ap); // clean up
return tot;
}
1)首先创建一个参数列表ap,也就是一个字符型指针;
2)初始化参数列表,其中va_start (ap, lim)中的lim不仅指定了该变参函数中参数的个数(次要的),更主要的是将其地址赋给ap,以便其能操作栈中的各个操作数(理想情况下在栈中会以此存储lim,及...所代表的各参数),但问题在于ap指向的是lim,而非我们真正要用到的参数,于是
int i = ( sizeof(n_values) + sizeof(int) - 1 ) & ~( sizeof(int) - 1 ); 就起作用了,它计算出了lim在栈中所占用的字节数n,然后使ap偏移n各位置,就能找到我们所要操作的数了.并不是楼主所说的字节对齐 .
我说的仅仅是个大概的意思,也是今天看了这个帖子之后给我的启发.如果有说得不对的地方还请大家指出来,一起学习.
经典,mark一下