作者:陈曦
日期:2012-7-28 12:20:17
环境:[Mac 10.7.1 Lion Intel i3 支持64位指令 gcc4.2.1 xcode4.2]
转载请注明出处
Q1: 可变参数的函数调用能够被正确执行的本质原因是什么?
A: 可变参数的一个重要特点就是参数个数不确定,但是最终可以被正确执行一般需要堆栈以及参数类型的确定性支持。如果参数类型都无法确定是某种或者某个范围内,那么可变参数函数是无法实现的。
Q2: 举个可变参数的例子吧。
A: 比如,求一组整形数的平均数:
get_average(2, 100, 1000), 第一个参数表示求平均数的整数个数为2个,后面跟着2个整数100和1000;
如果是求3个数的平均数,调用如下:
get_average(2, 100, 1000, 200).
代码如下:
#include
#include
#include
#include
#define PRINT_D(longValue) printf(#longValue" is %ld\n", ((long)longValue));
#define PRINT_STR(str) printf(#str" is %s\n", (str));
#define PRINT_DBL(doubleValue) printf(#doubleValue" is %f\n", (doubleValue));
double get_average(int int_num_count, ...)
{
va_list list;
double sum = 0;
int int_num_count_bak = int_num_count;
va_start(list, int_num_count);
while(int_num_count > 0)
{
sum += va_arg(list, int);
--int_num_count;
}
va_end(list);
return sum / int_num_count_bak;
}
int main()
{
double ret = get_average(2, 3, 4);
PRINT_DBL(ret)
ret = get_average(3, 3, 4, 5);
PRINT_DBL(ret)
return 0;
}
Q3: get_average函数原型最后的...就是表示可变参数?
A: 是的。也可以参考printf函数的原型:
int printf(const char * __restrict, ...) __DARWIN_LDBL_COMPAT(printf) __printflike(1, 2);
Q4: va_list, va_start, va_arg和va_end,它们实现了什么?
A: 它们分别实现了从堆栈中获取参数的首地址,依次获取不同参数的地址,最后结束处理的操作。
Q5: va_list等几个类型和函数(或者宏)的内部是如何实现的?
A: 在mac下,它们被宏定义为另外一个类型,内部的具体实现没有公开。下面将windows的实现列出(部分代码):
#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) )
#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)
va_start会定位到第一个可变参数的地址位置;
va_arg会根据参数类型获得此参数的值;
va_end做一个简单的置空操作,作为一个标志。
Q6: c语言函数的默认调用方式采用__cdecl,是否也间接支持了可变参数的实现?
A: 是的。再比如arm平台,在参数较少的情况下,参数是被优先传入寄存器的;如果超过一定的个数,那么参数采用入栈的方式。那么,对于上面的代码,在arm平台到底使用寄存器还是堆栈呢? 在xcode中创建一个ios工程,将上面的代码加入,编译得到它的arm汇编:
0x0009276c : sub sp, #12
0x0009276e : push {r4, r7, lr}
0x00092770 : add r7, sp, #4
0x00092772 : sub sp, #28
0x00092774 : mov r4, sp
0x00092776 : bic.w r4, r4, #7 ; 0x7
0x0009277a : mov sp, r4
0x0009277c : str r3, [r7, #16]
0x0009277e : str r2, [r7, #12]
0x00092780 : str r1, [r7, #8]
0x00092782 : add r1, sp, #20
0x00092784 : movs r2, #0
0x00092786 : movt r2, #0 ; 0x0
0x0009278a : vldr d16, [pc, #104] ; 0x927f6
0x0009278e : str r0, [sp, #24]
0x00092790 : vstr d16, [sp, #8]
0x00092794 : ldr r0, [sp, #24]
0x00092796 : str r0, [sp, #4]
0x00092798 : add.w r0, r7, #8 ; 0x8
0x0009279c : str r0, [r1, #0]
0x0009279e : str r2, [sp, #0]
0x000927a0 : movs r0, #0
0x000927a2 : movt r0, #0 ; 0x0
0x000927a6 : ldr r1, [sp, #24]
0x000927a8 : cmp r1, r0
0x000927aa : ble.n 0x927d2
0x000927ac : ldr r0, [sp, #20]
0x000927ae : adds r1, r0, #4
0x000927b0 : str r1, [sp, #20]
0x000927b2 : ldr r0, [r0, #0]
0x000927b4 : fmsr s0, r0
0x000927b8 : fsitod d16, s0
0x000927bc : vldr d17, [sp, #8]
0x000927c0 : faddd d16, d17, d16
0x000927c4 : vstr d16, [sp, #8]
0x000927c8 : ldr r0, [sp, #24]
0x000927ca : add.w r0, r0, #4294967295 ; 0xffffffff
0x000927ce : str r0, [sp, #24]
0x000927d0 : b.n 0x927a0
0x000927d2 : vldr d16, [sp, #8]
0x000927d6 : flds s0, [sp, #4]
0x000927da : fsitod d17, s0
0x000927de : fdivd d16, d16, d17
0x000927e2 : vmov r0, r1, d16
0x000927e6 : subs r4, r7, #4
0x000927e8 : mov sp, r4
0x000927ea : ldmia.w sp!, {r4, r7, lr}
0x000927ee : add sp, #12
0x000927f0 : bx lr
0x000927f2 : nop
0x000927f4 : lsls r0, r0, #0
0x000927f6 : lsls r0, r0, #0
0x000927f8 : lsls r0, r0, #0
0x000927fa : lsls r0, r0, #0
一些看起来动态的东西,在c语言那里是使用较为静态的东西来实现。
作者:陈曦
日期:2012-7-28 12:20:17
环境:[Mac 10.7.1 Lion Intel i3 支持64位指令 gcc4.2.1 xcode4.2]
转载请注明出处