<pre id="bbfd9"><del id="bbfd9"><dfn id="bbfd9"></dfn></del></pre>

          <ruby id="bbfd9"></ruby><p id="bbfd9"><mark id="bbfd9"></mark></p>

          <p id="bbfd9"></p>

          <p id="bbfd9"><cite id="bbfd9"></cite></p>

            <th id="bbfd9"><form id="bbfd9"><dl id="bbfd9"></dl></form></th>

            <p id="bbfd9"><cite id="bbfd9"></cite></p><p id="bbfd9"></p>
            <p id="bbfd9"><cite id="bbfd9"><progress id="bbfd9"></progress></cite></p>
            C語言

            C語言中變參函數的實現細節

            時間:2025-01-27 15:28:44 C語言 我要投稿
            • 相關推薦

            C語言中變參函數的實現細節

              C語言的函數雖然不具備C++的多態性,但也可以接受參數不確定的情況,當然,C語言中的變參函數實際在功能上是受限的,廢話不多講,下面來看看變參函數的邊邊角角的問題。以下僅供參考!

              討論之前我們來看一下最熟悉的變參函數printf的原型聲明:

              --------------------------------------------------------------------------------

            1
            int printf(const char *format, ...);        

              --------------------------------------------------------------------------------

              注意到,在函數中聲明其參數是可變的方法是三個點“...”,但同時,這個函數必須要有一個固定的參數,比如printf里面的這個format,也就是說變參函數的參數數目至少是一個。這是由C語言中實現變參的原理---計算堆棧地址---決定的。順著printf函數我們來看看它的定義是什么:

              --------------------------------------------------------------------------------

            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            15
            16
            17
            int __printf(const char *format, ...)        
             
            {        
             
            va_list arg;        
             
            int done;        
             
            va_start(arg, format);        
             
            done = vfprintf(stdout, format, arg);        
             
            va_end(arg);        
             
            return done;        
             
            }        

              --------------------------------------------------------------------------------

              (注意到庫函數中內部定義的變量和函數用了雙下劃線開頭,這也是我們寫應用程序時盡量不要用雙下劃線開頭的原因,我們也不應該使用單下劃線開頭的函數和變量,因為那也是系統保留的)

              其中發現__printf函數里用了va_list,va_start,va_end等宏,事實上,在__printf中調用的vfpirntf函數還用到了一個叫做va_arg的宏,這幾個宏就是編寫變參函數的關鍵。現在我們自己寫一個最簡單的變參函數,先來個感性認識:

              --------------------------------------------------------------------------------

            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            15
            16
            17
            18
            19
            20
            21
            22
            23
            24
            25
            26
            27
            28
            29
            30
            31
            32
            33
            #include        
             
            #include        
             
            void simple_va_fun(int i, ...)        
             
            {        
             
            va_list arg_ptr; //定義一個用來指向函數變參列表的指針arg_ptr        
             
            int j;        
             
            va_start(arg_ptr, i); //使arg_ptr指向第一個可變參數        
             
            j = va_arg(arg_ptr, int); //取得arg_ptr當前所指向的參數的值,并使arg_ptr指向下一個參數        
             
            va_end(arg_ptr); //指示提取參數結束        
             
            printf("%d %d ", i, j);        
             
            return;        
             
            }        
             
            int main(void)        
             
            {        
             
            simple_va_fun(3, 4);        
             
            return 0;        
             
            }        

              --------------------------------------------------------------------------------

              如代碼中的注釋所示,arg_ptr實際上是一個指向函數變參列表的指針,va_list實際上是void指針類型。

              va_start用來初始化這個指針,使之指向變參列表中的第一個參數,注意到它的第二個參數是變參函數的那個固定參數。

              va_arg利用已經初始化了的arg_ptr指針來取得變參列表中各個參數的值,第一個參數是變參列表指針,第二個參數是當前參數的類型。

              va_end宏用來提示結束參數結束,在LINUX的glibc實現中,va_end實際上就是一個空語句(void)0

              各個宏定義在頭文件stdarg.h中聲明,因此我們需要包含這個頭文件。其具體的定義如下:

              --------------------------------------------------------------------------------

            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            #define _AUPBND (sizeof(acpi_native_int) - 1)        
             
            #define _ADNBND (sizeof(acpi_native_int) - 1)        
             
            #define _bnd(X, bnd) (((sizeof(X)) + (bnd)) & (~(bnd)))        
             
            #define va_start(ap, A) (void)((ap) = (((char *)&(A)) + (_bnd(A, _AUPBND)))        
             
            #defind va_arg(ap, T) (*(T*)(((ap) += (_bnd(T, _AUPBND))) - (_bnd(T, _ADNBDN))))        
             
            #define va_end(ap) (void)0        

              --------------------------------------------------------------------------------

              這些宏定義都比較繁瑣,主要目的是為了適應不同系統的地址對齊問題。

              上面說過,va_start的功能實際上是使ap指針指向第一個變參,A就是我們的第一個固定參數,不考慮地址對齊,最簡單的辦法當然如下:

              ap = &A + sizeof(A)

              上述代碼其實也是實現的這個簡單的功能,但經過宏_AUPBND和_bnd之后,就能保證ap指向的地址至少是關于acpi_native_int對齊的,打個比方,如果此時A的地址是0x0003,而且A的類型占用4個字節,而當前系統要求4個字節對齊,那么就讓_AUPBND中的sizeof參數為4,經過多次宏替代之后ap的地址值就會是0x0008,而簡單地用上面的算式ap = &A + sizeof(A)計算出的結果是0x0007。

              同樣地,va_arg宏替代在不考慮任何移植性問題時,要取得當前變參的值并使指針指向下一個參數最簡單的辦法如下:

              *((ap+=sizeof(T)) - sizeof(T))

              這個需要稍微解釋一下,首先,C里面的參數壓棧是從右到左順序壓棧的,因此可以想象,第一個固定參數在棧頂(LINUX進程映像中棧是倒著增長的,這個地址是所有參數中最小的),第二個參數(也就是第一個變參)在緊接著固定參數之上,以此類推。因此,要想ap指針不斷指向下一個參數,就必須讓它每次都加上當前指向的變量所占內存的大小即 ap+=sizeof(T) 的含義。

              接下來,利用這個地址值又減去sizeof(T),實際上地址值又回到上一個參數處(注意,此時ap指針的值并未改變,也就是說,va_arg宏實現獲取第一個變參的值的時候是先使ap指向第二個變參,然后再去獲取第一個變參的值),然后取值。

              va_end宏就比較簡單了,雖然各種平臺的實現細節不一樣,但是道理都是一樣的,在glibc中va_end被簡單地實現為一個空語句。

              由此可見,實際上C語言的所謂變參函數是很笨的,它基本上啥智能都沒有,不能跟C++的多態性和符號重載相比,我們在傳遞參數的時候雖然可以傳遞不定個數的參數,但是這些參數都必須在函數實現中給予一一處理。所以我還是比較推崇C++呵呵!

              至于printf這個調皮鬼,上面看到它的原型了,里面還調用了vfprintf函數,這個函數就不分析了(實在太長了),它里面就用了va_arg來獲取各個變參的值。printf之所以可以識別各種變量類型,是因為你調用它的時候必須用printf修飾符,也就是%d,%f,%s等等來指定你的參數,printf是很笨的,它是不知道的。

            【C語言中變參函數的實現細節】相關文章:

            C語言中實現參數個數可變函數11-12

            C語言中返回字符串函數的實現方法09-19

            C語言中函數的區分08-30

            C語言中gets()函數知識08-10

            C語言中關于時間的函數10-24

            C語言中strpbr()函數的用法07-25

            c語言中time函數的用法08-27

            C語言中isalnum()函數和isalpha()函數的對比10-12

            C 語言中返回字符串函數的四種實現方法09-18

                    <pre id="bbfd9"><del id="bbfd9"><dfn id="bbfd9"></dfn></del></pre>

                    <ruby id="bbfd9"></ruby><p id="bbfd9"><mark id="bbfd9"></mark></p>

                    <p id="bbfd9"></p>

                    <p id="bbfd9"><cite id="bbfd9"></cite></p>

                      <th id="bbfd9"><form id="bbfd9"><dl id="bbfd9"></dl></form></th>

                      <p id="bbfd9"><cite id="bbfd9"></cite></p><p id="bbfd9"></p>
                      <p id="bbfd9"><cite id="bbfd9"><progress id="bbfd9"></progress></cite></p>
                      飘沙影院