作者:陈曦 日期:2012-7-2812:20:17 环境:[Mac 10.7.1 LionIntel i3 支持64位指令 gcc4.2.1xcode4.2] 转载请注明出处 Q1: 可变参数的函数调用能够被正确执行的本质原因是什么? A: 可变参数的一个重要特点就是参数个数不确定,但是最终可以被正确执行一般需要堆栈以及参数类型的确定性支持。如果参数类型都无法确定是某种或者某个范围内,那么可变参数函数是无法实现的。 Q2: 举个可变参数的例子吧。 A: 比如,求一组整形数的平均数: get_average(2, 100, 1000), 第一个参数表示求平均数的整数个数为2个,后面跟着2个整数100和1000; 如果是求3个数的平均数,调用如下: get_average(2, 100, 1000, 200). 代码如下: 1. #include 2. #include 3. #include 4. #include 5. 6. #define PRINT_D(longValue) printf(#longValue" is %ld\n", ((long)longValue)); 7. #define PRINT_STR(str) printf(#str" is %s\n", (str)); 8. #define PRINT_DBL(doubleValue) printf(#doubleValue" is %f\n", (doubleValue)); 9. 10. double get_average(int int_num_count, ...) 11. { 12. va_list list; 13. double sum = 0; 14. int int_num_count_bak = int_num_count; 15. 16. va_start(list, int_num_count); 17. while(int_num_count > 0) 18. { 19. sum += va_arg(list, int); 20. --int_num_count; 21. } 22. 23. va_end(list); 24. return sum / int_num_count_bak; 25. } 26. 27. int main() 28. { 29. double ret = get_average(2, 3, 4); 30. PRINT_DBL(ret) 31. 32. ret = get_average(3, 3, 4, 5); 33. PRINT_DBL(ret) 34. 35. return 0; 36. }
Q3: get_average函数原型最后的...就是表示可变参数? A: 是的。也可以参考printf函数的原型: 1. 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的实现列出(部分代码): 1. #ifndef _VA_LIST_DEFINED 2. #ifdef _M_ALPHA 3. typedef struct { 4. char *a0; /* pointer to first homed integer argument */ 5. int offset; /* byte offset of next parameter */ 6. } va_list; 7. #else 8. typedef char * va_list; 9. #endif 10. #define _VA_LIST_DEFINED 11. #endif 12. 13. #ifdef _M_IX86 14. 15. 16. #define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) 17. 18. #define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) ) 19. #define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) 20. #define va_end(ap) ( ap = (va_list)0 ) 21. 22. #elif defined(_M_MRX000)
可以看出,va_list就是一个类似char *的结构,保存参数的首地址(可能还包含下一个参数的偏移)信息;
va_start会定位到第一个可变参数的地址位置; va_arg会根据参数类型获得此参数的值; va_end做一个简单的置空操作,作为一个标志。 Q6: c语言函数的默认调用方式采用__cdecl,是否也间接支持了可变参数的实现? A: 是的。再比如arm平台,在参数较少的情况下,参数是被优先传入寄存器的;如果超过一定的个数,那么参数采用入栈的方式。那么,对于上面的代码,在arm平台到底使用寄存器还是堆栈呢? 在xcode中创建一个ios工程,将上面的代码加入,编译得到它的arm汇编: 1. 0x0009276c : sub sp, #12 2. 0x0009276e : push {r4, r7, lr} 3. 0x00092770 : add r7, sp, #4 4. 0x00092772 : sub sp, #28 5. 0x00092774 : mov r4, sp 6. 0x00092776 : bic.w r4, r4, #7 ; 0x7 7. 0x0009277a : mov sp, r4 8. 0x0009277c : str r3, [r7, #16] 9. 0x0009277e : str r2, [r7, #12] 10. 0x00092780 : str r1, [r7, #8] 11. 0x00092782 : add r1, sp, #20 12. 0x00092784 : movs r2, #0 13. 0x00092786 : movt r2, #0 ; 0x0 14. 0x0009278a : vldr d16, [pc, #104] ; 0x927f6 15. 0x0009278e : str r0, [sp, #24] 16. 0x00092790 : vstr d16, [sp, #8] 17. 0x00092794 : ldr r0, [sp, #24] 18. 0x00092796 : str r0, [sp, #4] 19. 0x00092798 : add.w r0, r7, #8 ; 0x8 20. 0x0009279c : str r0, [r1, #0] 21. 0x0009279e : str r2, [sp, #0] 22. 0x000927a0 : movs r0, #0 23. 0x000927a2 : movt r0, #0 ; 0x0 24. 0x000927a6 : ldr r1, [sp, #24] 25. 0x000927a8 : cmp r1, r0 26. 0x000927aa : ble.n 0x927d2 27. 0x000927ac : ldr r0, [sp, #20] 28. 0x000927ae : adds r1, r0, #4 29. 0x000927b0 : str r1, [sp, #20] 30. 0x000927b2 : ldr r0, [r0, #0] 31. 0x000927b4 : fmsr s0, r0 32. 0x000927b8 : fsitod d16, s0 33. 0x000927bc : vldr d17, [sp, #8] 34. 0x000927c0 : faddd d16, d17, d16 35. 0x000927c4 : vstr d16, [sp, #8] 36. 0x000927c8 : ldr r0, [sp, #24] 37. 0x000927ca : add.w r0, r0, #4294967295 ; 0xffffffff 38. 0x000927ce : str r0, [sp, #24] 39. 0x000927d0 : b.n 0x927a0 40. 0x000927d2 : vldr d16, [sp, #8] 41. 0x000927d6 : flds s0, [sp, #4] 42. 0x000927da : fsitod d17, s0 43. 0x000927de : fdivd d16, d16, d17 44. 0x000927e2 : vmov r0, r1, d16 45. 0x000927e6 : subs r4, r7, #4 46. 0x000927e8 : mov sp, r4 47. 0x000927ea : ldmia.w sp!, {r4, r7, lr} 48. 0x000927ee : add sp, #12 49. 0x000927f0 : bx lr 50. 0x000927f2 : nop 51. 0x000927f4 : lsls r0, r0, #0 52. 0x000927f6 : lsls r0, r0, #0 53. 0x000927f8 : lsls r0, r0, #0 54. 0x000927fa : lsls r0, r0, #0
在这里,我们可以看到,里面使用了堆栈寄存器sp,并从sp相关的内存中取参数操作,可以确定,依然采用堆栈方式传递参数的。
一些看起来动态的东西,在c语言那里是使用较为静态的东西来实现。 作者:陈曦 日期:2012-7-2812:20:17 环境:[Mac 10.7.1 LionIntel i3 支持64位指令 gcc4.2.1xcode4.2] 转载请注明出处
[ 本帖最后由 tiankai001 于 2012-12-15 08:02 编辑 ] |