作者:陳曦
日期: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_ALPHAtypedef struct { char *a0; /* pointer to first homed integer argument */ int offset; /* byte offset of next parameter */} va_list;#elsetypedef 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>:subsp, #120x0009276e <get_average+2>:push{r4, r7, lr}0x00092770 <get_average+4>:addr7, sp, #40x00092772 <get_average+6>:subsp, #280x00092774 <get_average+8>:movr4, sp0x00092776 <get_average+10>:bic.wr4, r4, #7; 0x70x0009277a <get_average+14>:movsp, r40x0009277c <get_average+16>:strr3, [r7, #16]0x0009277e <get_average+18>:strr2, [r7, #12]0x00092780 <get_average+20>:strr1, [r7, #8]0x00092782 <get_average+22>:addr1, sp, #200x00092784 <get_average+24>:movsr2, #00x00092786 <get_average+26>:movtr2, #0; 0x00x0009278a <get_average+30>:vldrd16, [pc, #104]; 0x927f6 <get_average+138>0x0009278e <get_average+34>:strr0, [sp, #24]0x00092790 <get_average+36>:vstrd16, [sp, #8]0x00092794 <get_average+40>:ldrr0, [sp, #24]0x00092796 <get_average+42>:strr0, [sp, #4]0x00092798 <get_average+44>:add.wr0, r7, #8; 0x80x0009279c <get_average+48>:strr0, [r1, #0]0x0009279e <get_average+50>:strr2, [sp, #0]0x000927a0 <get_average+52>:movsr0, #00x000927a2 <get_average+54>:movtr0, #0; 0x00x000927a6 <get_average+58>:ldrr1, [sp, #24]0x000927a8 <get_average+60>:cmpr1, r00x000927aa <get_average+62>:ble.n0x927d2 <get_average+102>0x000927ac <get_average+64>:ldrr0, [sp, #20]0x000927ae <get_average+66>:addsr1, r0, #40x000927b0 <get_average+68>:strr1, [sp, #20]0x000927b2 <get_average+70>:ldrr0, [r0, #0]0x000927b4 <get_average+72>:fmsrs0, r00x000927b8 <get_average+76>:fsitodd16, s00x000927bc <get_average+80>:vldrd17, [sp, #8]0x000927c0 <get_average+84>:fadddd16, d17, d160x000927c4 <get_average+88>:vstrd16, [sp, #8]0x000927c8 <get_average+92>:ldrr0, [sp, #24]0x000927ca <get_average+94>:add.wr0, r0, #4294967295; 0xffffffff0x000927ce <get_average+98>:strr0, [sp, #24]0x000927d0 <get_average+100>:b.n0x927a0 <get_average+52>0x000927d2 <get_average+102>:vldrd16, [sp, #8]0x000927d6 <get_average+106>:fldss0, [sp, #4]0x000927da <get_average+110>:fsitodd17, s00x000927de <get_average+114>:fdivdd16, d16, d170x000927e2 <get_average+118>:vmovr0, r1, d160x000927e6 <get_average+122>:subsr4, r7, #40x000927e8 <get_average+124>:movsp, r40x000927ea <get_average+126>:ldmia.wsp!, {r4, r7, lr}0x000927ee <get_average+130>:addsp, #120x000927f0 <get_average+132>:bxlr0x000927f2 <get_average+134>:nop0x000927f4 <get_average+136>:lslsr0, r0, #00x000927f6 <get_average+138>:lslsr0, r0, #00x000927f8 <get_average+140>:lslsr0, r0, #00x000927fa <get_average+142>:lslsr0, r0, #0
在這裡,我們可以看到,裡面使用了堆棧寄存器sp,並從sp相關的記憶體中取參數操作,可以確定,依然採用堆棧方式傳遞參數的。
一些看起來動態東西,在c語言那裡是使用較為靜態東西來實現。
作者:陳曦
日期:2012-7-28 12:20:17
環境:[Mac 10.7.1 Lion Intel i3 支援64位指令 gcc4.2.1 xcode4.2]
轉載請註明出處