可变参数, 它依赖于堆栈----小话c语言(23)

作者:陈曦

日期: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 <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>

#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_list就是一个类似char *的结构,保存参数的首地址(可能还包含下一个参数的偏移)信息;

va_start会定位到第一个可变参数的地址位置;

va_arg会根据参数类型获得此参数的值;

va_end做一个简单的置空操作,作为一个标志。


Q6: c语言函数的默认调用方式采用__cdecl,是否也间接支持了可变参数的实现?

A: 是的。再比如arm平台,在参数较少的情况下,参数是被优先传入寄存器的;如果超过一定的个数,那么参数采用入栈的方式。那么,对于上面的代码,在arm平台到底使用寄存器还是堆栈呢? 在xcode中创建一个ios工程,将上面的代码加入,编译得到它的arm汇编:

0x0009276c <get_average+0>:	sub	sp, #12
0x0009276e <get_average+2>:	push	{r4, r7, lr}
0x00092770 <get_average+4>:	add	r7, sp, #4
0x00092772 <get_average+6>:	sub	sp, #28
0x00092774 <get_average+8>:	mov	r4, sp
0x00092776 <get_average+10>:	bic.w	r4, r4, #7	; 0x7
0x0009277a <get_average+14>:	mov	sp, r4
0x0009277c <get_average+16>:	str	r3, [r7, #16]
0x0009277e <get_average+18>:	str	r2, [r7, #12]
0x00092780 <get_average+20>:	str	r1, [r7, #8]
0x00092782 <get_average+22>:	add	r1, sp, #20
0x00092784 <get_average+24>:	movs	r2, #0
0x00092786 <get_average+26>:	movt	r2, #0	; 0x0
0x0009278a <get_average+30>:	vldr	d16, [pc, #104]	; 0x927f6 <get_average+138>
0x0009278e <get_average+34>:	str	r0, [sp, #24]
0x00092790 <get_average+36>:	vstr	d16, [sp, #8]
0x00092794 <get_average+40>:	ldr	r0, [sp, #24]
0x00092796 <get_average+42>:	str	r0, [sp, #4]
0x00092798 <get_average+44>:	add.w	r0, r7, #8	; 0x8
0x0009279c <get_average+48>:	str	r0, [r1, #0]
0x0009279e <get_average+50>:	str	r2, [sp, #0]
0x000927a0 <get_average+52>:	movs	r0, #0
0x000927a2 <get_average+54>:	movt	r0, #0	; 0x0
0x000927a6 <get_average+58>:	ldr	r1, [sp, #24]
0x000927a8 <get_average+60>:	cmp	r1, r0
0x000927aa <get_average+62>:	ble.n	0x927d2 <get_average+102>
0x000927ac <get_average+64>:	ldr	r0, [sp, #20]
0x000927ae <get_average+66>:	adds	r1, r0, #4
0x000927b0 <get_average+68>:	str	r1, [sp, #20]
0x000927b2 <get_average+70>:	ldr	r0, [r0, #0]
0x000927b4 <get_average+72>:	fmsr	s0, r0
0x000927b8 <get_average+76>:	fsitod	d16, s0
0x000927bc <get_average+80>:	vldr	d17, [sp, #8]
0x000927c0 <get_average+84>:	faddd	d16, d17, d16
0x000927c4 <get_average+88>:	vstr	d16, [sp, #8]
0x000927c8 <get_average+92>:	ldr	r0, [sp, #24]
0x000927ca <get_average+94>:	add.w	r0, r0, #4294967295	; 0xffffffff
0x000927ce <get_average+98>:	str	r0, [sp, #24]
0x000927d0 <get_average+100>:	b.n	0x927a0 <get_average+52>
0x000927d2 <get_average+102>:	vldr	d16, [sp, #8]
0x000927d6 <get_average+106>:	flds	s0, [sp, #4]
0x000927da <get_average+110>:	fsitod	d17, s0
0x000927de <get_average+114>:	fdivd	d16, d16, d17
0x000927e2 <get_average+118>:	vmov	r0, r1, d16
0x000927e6 <get_average+122>:	subs	r4, r7, #4
0x000927e8 <get_average+124>:	mov	sp, r4
0x000927ea <get_average+126>:	ldmia.w	sp!, {r4, r7, lr}
0x000927ee <get_average+130>:	add	sp, #12
0x000927f0 <get_average+132>:	bx	lr
0x000927f2 <get_average+134>:	nop
0x000927f4 <get_average+136>:	lsls	r0, r0, #0
0x000927f6 <get_average+138>:	lsls	r0, r0, #0
0x000927f8 <get_average+140>:	lsls	r0, r0, #0
0x000927fa <get_average+142>:	lsls	r0, r0, #0

在这里,我们可以看到,里面使用了堆栈寄存器sp,并从sp相关的内存中取参数操作,可以确定,依然采用堆栈方式传递参数的。


一些看起来动态的东西,在c语言那里是使用较为静态的东西来实现。


作者:陈曦

日期:2012-7-28 12:20:17 

环境:[Mac 10.7.1 Lion Intel i3 支持64位指令 gcc4.2.1 xcode4.2]  

转载请注明出处


    

你可能感兴趣的:(可变参数, 它依赖于堆栈----小话c语言(23))