变参数函数例如printf平时也许用得很多,但她存在一个问题,假如在编译之前不知道参数数目就无法调用,比如有一个函数testfun,它的参数数目不定,参数(s)是运行时从配置文件中得到,那么怎样调用这个函数呢?一个想法简单但无法圆满实现的做法:
返回类型
testfun( int 编号, ... )
{
......
}
int main( void
)
{
返回类型 rv;
if( 运行时知道此编号需要1个参数 )
rv = testfun( 此编号,
参数1 );
else if( 运行时知道此编号需要2个参数 )
rv = testfun( 此编号, 参数1, 参数2
);
else if( 运行时知道此编号需要3个参数 )
rv = testfun( 此编号, 参数1, 参数2, 参数3
);
……
}
这样的代码即使你能容忍它的体积,你也无法全部写完,因为即使你写了100行能处理100个参数,但你也无法保证实际不会出现101个参数。
C/C++语法对这个问题是无能为力,但C/C++扩展是提供ASM支持的,如果不考虑移植的问题的话可以自己模拟_cdecl的操作
返回类型
testfun( int 编号, ... )
{
......
}
int main( void
)
{
for( int i=0; i<运行时知道此编号需要的参数数目n; ++i )
__asm push
参数n-i // 这只是个演示,后面将仔细说明
返回类型 rv = testfun( 此编号
);
__asm add esp, 所有参数占用的栈字节数 //
这只是个演示,后面将仔细说明
}
这样就简洁多了,且无论参数数目有多少都能全部解决,只是使用汇编模拟了一下_cdecl的压栈出栈操作而已。
说明:
[1]:只要是变参数函数就一定是_cdecl方式,即使申明它为_stdcall也没用。
[2]:_cdecl的实参是从右向左依次压入栈,由调用者清理栈,所以在函数结束之后需要将esp加上所有参数占用的栈字节数,这其中返回地址和第一个参数占用的栈字节数在这段代码中会由编译器自动释放,另一个更简单的做法是在push参数之前先保存好esp值,在函数调用结束之后再将esp值恢复。
[3]:参数压栈的规则:
a.
整型量如果不足int长度,就扩展成int/unsigned。(整型量指的是char、unsigned、enum等)(扩展成int/unsigned和以int长度对齐是不同的含义)。
b.
实参按int长度对齐(主要指自定义类型)。
c. float类型扩展成double入栈。
变参数压栈的特别规则:
a.
对于自定义类型,只进行位拷贝,而不调用copy
constructor。
[4]:类成员函数也可以是变参数函数。
[5]:ASM扩展不属于标准C++语法,因此不能移植,如果在GCC中就应该使用AT&T汇编,而不是Intel汇编。
阅读(2117) | 评论(15) | 转发(0) |