用指標處理C語言中不定數目的函數參數

來源:互聯網
上載者:User
用指標處理 C 語言中不定數目的函數參數 現在我們每編一個程式幾乎都會用到兩個函數-printf和scanf。發現這兩個函數和普通函數的不同之處了嗎?那就是這兩個函數都可以處理不定數目的實參。C語言是一種很寬鬆的語言,它甚至允許 程式員 對函數傳

用指標處理C語言中不定數目的函數參數

 

現在我們每編一個程式幾乎都會用到兩個函數-printf和scanf。發現這兩個函數和普通函數的不同之處了嗎?那就是這兩個函數都可以處理不定數目的實參。C語言是一種很寬鬆的語言,它甚至允許程式員對函數傳遞任意數目的參數。而這個特性在某些情況下是非常有用的。比如,現在我們要編一個求一系列整數平均值的函數average(),如果不用變長度實參,可能需要定義以下一系列原型:

         int  average_1  (  int  );

         int  average_2  (  int,  int  );

         int  average_3  (  int,  int,  int  );

         …

雖然C++的重載功能可以使這些函數變成同名的,但是編碼的工作量會變得很大,而且程式會變得沒有美感,況且這種類型的函數有無窮多種,怎麼定義得過來呢?現在,我們用變長度實參,可以唯寫一個函數,就能處理所有的情況。函數原型如下:

         int  average  (  int,  …  );

注意參數列表中的三個點是符合C語言的文法的,而不是我省略了什麼東西。這三個點出現在函數形式參數列表的最後,表示這以後可以向函數傳遞任意數目的實參。例如,函數scanf的聲明如下:

int __cdecl scanf (const char *format,…);

注:__cdecl聲明的是參數傳遞模式,因為__cdecl是預設的,因此可以省略.

注意,三個點必須出現在參數列表的最後,而且前面必須要定義至少一個的形式參數。現在我們可以通過函數調用average(0,1,2,3,4,5,-1);來求0到5的平均值了(最後一個-1是結束標誌,不參與求值運算,這一點在後面會講到)。

現在的問題是,雖然我們順利地聲明了函數原型,並且也順利地調用了函數,但是怎樣在函數中接受這些參數呢?不幸的是C語言本身並沒有提供像聲明參數那樣簡單的接受參數的機制,顯然,我們不可能只用一個省略符號來接受這些參數。但是我們可以用指標來解決這個問題(用嵌入式彙編也可以解決這個問題,但是那超出了C語言的範疇)。先讓我們來看一下C語言函數傳遞參數的機制。

C語言中的函數一般①是通過把參數拷貝到一塊特殊的記憶體地區(稱為堆棧)內傳遞給函數的。比如,函數調用

scanf( “%d %f %c %C %s %S”, &i, &fp, &c, &wc, s, ws );

它的參數在記憶體中的樣子是:

地址

參數

……

……

0x0012f308

format

0x0012f30c

&i

0x0012f310

&fp

0x0012f314

&c

0x0012f318

&wc

0x0012f31c

S

0x0012f320

ws

……

……

(*)format是一個指向字串“%d %f %c %C %s %S”的指標

注:這裡討論的情況只對c語言預設的參數傳遞模式__cdecl成立,而不適用於__fastcall和__stdcall兩種模式.現在我們在寫的函數全都是__cdecl模式的。

而 format的地址我們可以用&format得到,所以以後各個參數的地址就可以通過增加指標&format的值來得到了。例如,&c的地址是p= (char**)((char*)&format+sizeof(char**)+sizeof(int**)+sizeof(float**))/*這 個式子夠煩的:-)*/,而&c的值就是*p了。因為C語言沒有提供關於參數數目的資訊,所以這個資訊要有程式員傳遞一個參數來實現。“%d %f %c %C %s %S”就是告訴scanf,它後面還有六個各種類型的指標作為參數。上面對函數average傳遞的最後一個參數-1也是用來作為結束標誌的。

說 到這兒,有一個問題我們不得不提,就是我們又一次遇到了對齊(alignment)問題。所謂對齊,對Intel80x86機器來說就是要求每個變數的地 址都是sizeof(int)的倍數。在32位(4位元組)機器上表現為所有的變數地址都能被4整除。這樣,變數在記憶體中就不一定是緊密排列的了。例如,下 面的函數:

int  Bad_Example  (  char  c,  int  i  );

 

如果參數c的地址是0x0012f308的話,i的地址就不是0x0012f309,而是被移到了0x0012f30c。一般的,如果參數type p1的地址是&p1的話,那麼它的後繼參數的地址可以通過在他後面加上一個位移

( char * ) & p1 + ( sizeof ( type ) + sizeof( int ) –1 ) /sizeof ( int ) * sizeof ( int )

得到。(注意,在加的時侯一定要先把&p1的類型轉化為(char*)。)

在標頭檔<stdarg.h>中定義了宏—INTSIZEOF()來得到這個位移值:

#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )

這裡為了加快運算用了位操作,但功能是一樣的。現在,假設這個宏已經定義了,我們來實現average函數作為一個例子:

int average ( int n, ... )

{

    int sum = 0, c = 0;

    int *p = &n;

    if ( n < 0 ) return 0;

    while ( *p >= 0 )

    {

            sum += *p;

            c++;

            ( char * ) p += _INTSIZEOF(int);

    };

    return sum/c;

}

由於這種操作具有通用性,所以ANSI C提供了三個宏來實現這種處理。這組宏定義在標頭檔<stdarg.h>中。

type va_arg ( va_list arg_ptr, type );

void va_end( va_list arg_ptr );

void va_start( va_list arg_ptr, prev_param );

操作方法如下:

先定義一個va_list類型的變數,然後用宏va_start給他賦初值,prev_param用省略符號前的參數名代替。然後用宏va_arg來挨個取得參數的值,參數的類型在type中指定。最後用宏va_end釋放變數。

下面是函數average的另一種實現方式:

int average ( int n, ... )

{

    int sum = n, count = 1, p;

    va_list arg_ptr;

    if ( n < 0 ) return 0;

    va_start( arg_ptr, n );

    while( ( p = va_arg( arg_ptr, int ) ) >= 0 )

    {

           sum += p;

            count++;

    }

    va_end( arg_ptr );

    return sum/count;

}

最後還需要說明兩點。首先,上面講的內容與C 語言的實現有關,而C語言的實現又依賴於cpu和作業系統,所以並不適用於所有的電腦,而且隨機器的不同會由很大的區別。在<stdarg.h>內會看 到大量的條件編譯就是這個原因。其次,雖然函數定義中使用可變參數列表提供了很大的靈活性,但是對可變部分的參數C語言編譯器不會進行類型檢查,所以程式 中要特別小心,確保參數的傳遞和接受是正確的。

 

:

① C語言中還有一種參數傳遞方式稱為__fastcall方式。這種方式是通過盡量把參數放入寄存器內來傳遞的(因為寄存器數目有限,用完後剩下的參數只能放入記憶體了),所以用這種方式傳遞參數會提高程式的效率

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.