可變參數, 它依賴於堆棧—-小話c語言(23)

來源:互聯網
上載者:User

作者:陳曦

日期: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]  

轉載請註明出處

    

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.