• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            浪跡天涯

            唯有努力...
            努力....再努力...

            va_list、va_start、va_arg、va_end的原理與使用

            1. 概述
              由于在C語言中沒有函數重載,解決不定數目函數參數問題變得比較麻煩;即使采用C++,如果參數個數不能確定,也很難采用函數重載.對這種情況,有些人采用指針參數來解決問題.下面就c語言中處理不定參數數目的問題進行討論.
            2. 定義
              大家先看幾宏.
              在VC++6.0的include有一個stdarg.h頭文件,有如下幾個宏定義:
              #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 )                            // 將指針置為無效
              如果對以上幾個宏定義不理解,可以略過,接這看后面的內容.
            3. 參數在堆棧中分布,位置
              在進程中,堆棧地址是從高到低分配的.當執行一個函數的時候,將參數列表入棧,壓入堆棧的高地址部分,然后入棧函數的返回地址,接著入棧函數的執行代碼,這個入棧過程,堆棧地址不斷遞減,一些黑客就是在堆棧中修改函數返回地址,執行自己的代碼來達到執行自己插入的代碼段的目的.
              總之,函數在堆棧中的分布情況是:地址從高到低,依次是:函數參數列表,函數返回地址,函數執行代碼段.
              堆棧中,各個函數的分布情況是倒序的.即最后一個參數在列表中地址最高部分,第一個參數在列表地址的最低部分.參數在堆棧中的分布情況如下:
              最后一個參數
              倒數第二個參數
              ...
              第一個參數
              函數返回地址
              函數代碼段
            4. 示例代碼
              void arg_test(int i, ...);
              int main(int argc,char *argv[])
              {
               int int_size = _INTSIZEOF(int);
               printf("int_size=%d\n", int_size);
               arg_test(0, 4);
               
               arg_cnt(4,1,2,3,4);
               return 0;
              }
              void arg_test(int i, ...)
              {
               int j=0;
               va_list arg_ptr; 
               
               va_start(arg_ptr, i); 
               printf("&i = %p\n", &i);//打印參數i在堆棧中的地址
               printf("arg_ptr = %p\n", arg_ptr);
               //打印va_start之后arg_ptr地址,
               //應該比參數i的地址高sizeof(int)個字節
               //這時arg_ptr指向下一個參數的地址
               
               j=*((int *)arg_ptr);
               printf("%d %d\n", i, j); 
               j=va_arg(arg_ptr, int);
               printf("arg_ptr = %p\n", arg_ptr);
               //打印va_arg后arg_ptr的地址
               //應該比調用va_arg前高sizeof(int)個字節
               //這時arg_ptr指向下一個參數的地址
               va_end(arg_ptr);
               printf("%d %d\n", i, j);
              }

            5. 代碼說明:
              int int_size = _INTSIZEOF(int);得到int類型所占字節數
               va_start(arg_ptr, i); 得到第一個可變參數地址,

              根據定義(va_list)&v得到起始參數的地址, 再加上_INTSIZEOF(v) ,就是其實參數下一個參數的地址,即第一個可變參數地址.
              j=va_arg(arg_ptr, int); 得到第一個參參數的值,并且arg_ptr指針上移一個_INTSIZEOF(int),即指向下一個可變參數的地址.
              va_end(arg_ptr);置空arg_ptr,即arg_ptr=0;
              總結:讀取可變參數的過程其實就是堆棧中,使用指針,遍歷堆棧段中的參數列表,從低地址到高地址一個一個地把參數內容讀出來的過程.

            6. 在編程中應該注意的問題和解決辦法
              雖然可以通過在堆棧中遍歷參數列表來讀出所有的可變參數,但是由于不知道可變參數有多少個,什么時候應該結束遍歷,如果在堆棧中遍歷太多,那么很可能讀取一些無效的數據.
              解決辦法:a.可以在第一個起始參數中指定參數個數,那么就可以在循環還中讀取所有的可變參數;b.定義一個結束標記,在調用函數的時候,在最后一個參數中傳遞這個標記,這樣在遍歷可變參數的時候,可以根據這個標記結束可變參數的遍歷;
              下面是一段示例代碼:
              //第一個參數定義可選參數個數,用于循環取初參數內容
              void arg_cnt(int cnt, ...);
              int main(int argc,char *argv[])
              {
               int int_size = _INTSIZEOF(int);
               printf("int_size=%d\n", int_size);
               arg_cnt(4,1,2,3,4);
               return 0;
              }
              void arg_cnt(int cnt, ...)
              {
               int value=0;
               int i=0;
               int arg_cnt=cnt;
               va_list arg_ptr; 
               va_start(arg_ptr, cnt); 
               for(i = 0; i < cnt; i++)
               {
                value = va_arg(arg_ptr,int);
                printf("value%d=%d\n", i+1, value);
               }
              }

              雖然可以根據上面兩個辦法解決讀取參數個數的問題,但是如果參數類型都是不定的,該怎么辦,如果不知道參數的類型,即使讀到了參數也沒有辦法進行處理.解決辦法:可以自定義一些可能出現的參數類型,這樣在可變參數列表中,可以可變參數列表中的那類型,然后根據類型,讀取可變參數值,并進行準確地轉換.傳遞參數的時候可以這樣傳遞:參數數目,可變參數類型1,可變參數值1,可變參數類型2,可變參數值2,....
              這里給出一個完整的例子:
              #include <stdio.h>
              #include <stdarg.h>
              const int INT_TYPE  = 100000;
              const int STR_TYPE  = 100001;
              const int CHAR_TYPE  = 100002;
              const int LONG_TYPE  = 100003;
              const int FLOAT_TYPE = 100004;
              const int DOUBLE_TYPE = 100005;
              //第一個參數定義可選參數個數,用于循環取初參數內容
              //可變參數采用arg_type,arg_value...的形式傳遞,以處理不同的可變參數類型
              void arg_type(int cnt, ...);
              //第一個參數定義可選參數個數,用于循環取初參數內容
              void arg_cnt(int cnt, ...);
              //測試va_start,va_arg的使用方法,函數參數在堆棧中的地址分布情況
              void arg_test(int i, ...);
              int main(int argc,char *argv[])
              {
               int int_size = _INTSIZEOF(int);
               printf("int_size=%d\n", int_size);
               arg_test(0, 4);
               
               arg_cnt(4,1,2,3,4);
               arg_type(2, INT_TYPE, 222, STR_TYPE, "ok,hello world!");
               return 0;
              }

            void arg_test(int i, ...)
            {
             int j=0;
             va_list arg_ptr; 
             
             va_start(arg_ptr, i); 
             printf("&i = %p\n", &i);//打印參數i在堆棧中的地址
             printf("arg_ptr = %p\n", arg_ptr);
             //打印va_start之后arg_ptr地址,
             //應該比參數i的地址高sizeof(int)個字節
             //這時arg_ptr指向下一個參數的地址
             
             j=*((int *)arg_ptr);
             printf("%d %d\n", i, j); 
             j=va_arg(arg_ptr, int);
             printf("arg_ptr = %p\n", arg_ptr);
             //打印va_arg后arg_ptr的地址
             //應該比調用va_arg前高sizeof(int)個字節
             //這時arg_ptr指向下一個參數的地址
             va_end(arg_ptr);
             printf("%d %d\n", i, j);
            }
            void arg_cnt(int cnt, ...)
            {
             int value=0;
             int i=0;
             int arg_cnt=cnt;
             va_list arg_ptr; 
             va_start(arg_ptr, cnt); 
             for(i = 0; i < cnt; i++)
             {
              value = va_arg(arg_ptr,int);
              printf("value%d=%d\n", i+1, value);
             }
            }
            void arg_type(int cnt, ...)
            {
             int arg_type = 0;
             int int_value=0;
             int i=0;
             int arg_cnt=cnt; 
             char *str_value = NULL;
             va_list arg_ptr; 
             va_start(arg_ptr, cnt);
             for(i = 0; i < cnt; i++)
             {
              arg_type = va_arg(arg_ptr,int);
              switch(arg_type)
              {
              case INT_TYPE:
               int_value = va_arg(arg_ptr,int);
               printf("value%d=%d\n", i+1, int_value);
               break;
              case STR_TYPE:
               str_value = va_arg(arg_ptr,char*);
               printf("value%d=%d\n", i+1, str_value);
               break;
              default:
               break;
              }
             }
            }
            以上是我個人的見解,不對的地方希望大家指正,發表看法,我不勝感謝!!!

            posted on 2008-01-21 15:30 浪跡天涯 閱讀(62220) 評論(12)  編輯 收藏 引用 所屬分類: C++

            評論

            # re: va_list、va_start、va_arg、va_end的原理與使用 2008-01-21 15:30 浪跡天涯

            va_start() va_end()函數應用

            1:當無法列出傳遞函數的所有實參的類型和數目時,可用省略號指定參數表
            void foo(...);
            void foo(parm_list,...);

            2:函數參數的傳遞原理
            函數參數是以數據結構:棧的形式存取,從右至左入棧.eg:
            #include <iostream>
            void fun(int a, ...)
            {
            int *temp = &a;
            temp++;
            for (int i = 0; i < a; ++i)
            {
            cout << *temp << endl;
            temp++;
            }
            }

            int main()
            {
            int a = 1;
            int b = 2;
            int c = 3;
            int d = 4;
            fun(4, a, b, c, d);
            system("pause");
            return 0;
            }
            Output::
            1
            2
            3
            4

            3:獲取省略號指定的參數
            在函數體中聲明一個va_list,然后用va_start函數來獲取參數列表中的參數,使用完畢后調用va_end()結束。像這段代碼:
            void TestFun(char* pszDest, int DestLen, const char* pszFormat, ...)
            {
            va_list args;
            va_start(args, pszFormat);
            _vsnprintf(pszDest, DestLen, pszFormat, args);
            va_end(args);
            }

            4.va_start使argp指向第一個可選參數。va_arg返回參數列表中的當前參數并使argp指向參數列表中的下一個參數。va_end把argp指針清為NULL。函數體內可以多次遍歷這些參數,但是都必須以va_start開始,并以va_end結尾。

              1).演示如何使用參數個數可變的函數,采用ANSI標準形式
              #include 〈stdio.h〉
              #include 〈string.h〉
              #include 〈stdarg.h〉
              /*函數原型聲明,至少需要一個確定的參數,注意括號內的省略號*/
              int demo( char, ... );
              void main( void )
              {
               demo("DEMO", "This", "is", "a", "demo!", "");
              }
              /*ANSI標準形式的聲明方式,括號內的省略號表示可選參數*/
              int demo( char msg, ... )
              {
            /*定義保存函數參數的結構*/
               va_list argp;
               int argno = 0;
               char para;

               /*argp指向傳入的第一個可選參數,msg是最后一個確定的參數*/
               va_start( argp, msg );
               while (1)
            {
               para = va_arg( argp, char);
               if ( strcmp( para, "") == 0 )
            break;
               printf("Parameter #%d is: %s\n", argno, para);
               argno++;
               }
               va_end( argp );
               /*將argp置為NULL*/
               return 0;
              }

            2)//示例代碼1:可變參數函數的使用
            #include "stdio.h"
            #include "stdarg.h"
            void simple_va_fun(int start, ...)
            {
            va_list arg_ptr;
            int nArgValue =start;
            int nArgCout=0; //可變參數的數目
            va_start(arg_ptr,start); //以固定參數的地址為起點確定變參的內存起始地址。
            do
            {
            ++nArgCout;
            printf("the %d th arg: %d\n",nArgCout,nArgValue); //輸出各參數的值
            nArgValue = va_arg(arg_ptr,int); //得到下一個可變參數的值
            } while(nArgValue != -1);
            return;
            }
            int main(int argc, char* argv[])
            {
            simple_va_fun(100,-1);
            simple_va_fun(100,200,-1);
            return 0;
            }

            3)//示例代碼2:擴展——自己實現簡單的可變參數的函數。
            下面是一個簡單的printf函數的實現,參考了<The C Programming Language>中的例子
            #include "stdio.h"
            #include "stdlib.h"
            void myprintf(char* fmt, ...) //一個簡單的類似于printf的實現,//參數必須都是int 類型
            {
            char* pArg=NULL; //等價于原來的va_list
            char c;

            pArg = (char*) &fmt; //注意不要寫成p = fmt !!因為這里要對//參數取址,而不是取值
            pArg += sizeof(fmt); //等價于原來的va_start

            do
            {
            c =*fmt;
            if (c != '%')
            {
            putchar(c); //照原樣輸出字符
            }
            else
            {
            //按格式字符輸出數據
            switch(*++fmt)
            {
            case'd':
            printf("%d",*((int*)pArg));
            break;
            case'x':
            printf("%#x",*((int*)pArg));
            break;
            default:
            break;
            }
            pArg += sizeof(int); //等價于原來的va_arg
            }
            ++fmt;
            }while (*fmt != '\0');
            pArg = NULL; //等價于va_end
            return;
            }
            int main(int argc, char* argv[])
            {
            int i = 1234;
            int j = 5678;

            myprintf("the first test:i=%d\n",i,j);
            myprintf("the secend test:i=%d; %x;j=%d;\n",i,0xabcd,j);
            system("pause");
            return 0;
            }
              回復  更多評論   

            # re: va_list、va_start、va_arg、va_end的原理與使用 2009-05-14 08:05 ForE

            多謝!
            描述上有點問題
            -----------------------
            堆棧中,各個函數的分布情況是倒序的.即最后一個參數在列表中地址最高部分,第一個參數在列表地址的最低部分.參數在堆棧中的分布情況如下:
            最后一個參數
            倒數第二個參數
            ...
            第一個參數
            函數返回地址
            函數代碼段
            ---------------------
            stdcall才是這種情況...
              回復  更多評論   

            # re: va_list、va_start、va_arg、va_end的原理與使用 2009-06-09 13:53 code c

            經典!!!  回復  更多評論   

            # re: va_list、va_start、va_arg、va_end的原理與使用 2009-06-09 14:07 code c

            int demo( char msg, ... )
            這個函數有個小問題(應該是char *,而不是char ),應改為:
            /*ANSI標準形式的聲明方式,括號內的省略號表示可選參數*/
            int demo(char* msg, ...)
            {
            /*定義保存函數參數的結構*/
            va_list argp;
            int argno = 0;
            char* para;

            /*argp指向傳入的第一個可選參數,msg是最后一個確定的參數*/
            va_start(argp, msg);
            while (1)
            {
            para = va_arg(argp, char*);
            if (strcmp(para, "") == 0)
            break;
            printf("Parameter #%d is: %s\n", argno, para);
            argno++;
            }
            va_end(argp);
            /*將argp置為NULL*/
            return 0;
            }
              回復  更多評論   

            # re: va_list、va_start、va_arg、va_end的原理與使用 2010-02-08 21:05 chhaya

            好!  回復  更多評論   

            # re: va_list、va_start、va_arg、va_end的原理與使用 2010-04-15 11:29 某某

            謝謝分享,很詳細
            也是,參數壓棧順序好像有兩種啊  回復  更多評論   

            # re: va_list、va_start、va_arg、va_end的原理與使用 2010-05-14 17:43 Delacroix

            函數的代碼段是不放在堆棧里的。所有的執行代碼都是放在統一的代碼段里的。  回復  更多評論   

            # re: va_list、va_start、va_arg、va_end的原理與使用 2010-07-01 16:09 suohd

            @Delacroix
            這點我同意。  回復  更多評論   

            # re: va_list、va_start、va_arg、va_end的原理與使用 2010-07-08 13:39 leobupt

            @code c
            demo()函數有幾處錯誤,修改如下:

            int demo( char *msg, ... )
            {
            /*定義保存函數參數的結構*/
            va_list argp;
            int argno = 0;
            char *para; //char para;

            /*argp指向傳入的第一個可選參數,msg是最后一個確定的參數*/
            va_start( argp, msg );
            while (1)
            {
            para = va_arg( argp, char*);//para = va_arg( argp, char);
            if (strcmp(para, "") == 0 )
            break;
            printf("Parameter #%d is: %s\n", argno, para);
            argno++;
            }
            va_end(argp);
            //va_end( argp );   
            /*將argp置為NULL*/
            return 0;
            }   回復  更多評論   

            # re: va_list、va_start、va_arg、va_end的原理與使用 2011-03-08 16:35 cxm240

            最后一個參數
            倒數第二個參數
            ...
            第一個參數
            函數返回地址
            函數代碼段


            最后壓棧的不是函數代碼段,而是該函數的棧幀ebp  回復  更多評論   

            # re: va_list、va_start、va_arg、va_end的原理與使用[未登錄] 2011-03-25 08:34 liu

            TC中typedef void *va_list; 為什么不能用*va_list代替void  回復  更多評論   

            # re: va_list、va_start、va_arg、va_end的原理與使用 2012-02-01 09:50 liny

            樓主講的很詳細, 懂了, 謝謝...  回復  更多評論   

            <2008年1月>
            303112345
            6789101112
            13141516171819
            20212223242526
            272829303112
            3456789

            導航

            統計

            常用鏈接

            留言簿(22)

            隨筆分類(30)

            隨筆檔案(29)

            文章分類

            搜索

            積分與排名

            最新評論

            閱讀排行榜

            評論排行榜

            久久久久九九精品影院| 日本三级久久网| 亚洲精品乱码久久久久久蜜桃不卡| 无码任你躁久久久久久久| 理论片午午伦夜理片久久| 久久99久久99精品免视看动漫| 日韩精品久久久肉伦网站 | 久久e热在这里只有国产中文精品99| 国产成人精品久久亚洲高清不卡| 午夜精品久久久久成人| 精品久久久久久国产潘金莲| 精品国产乱码久久久久久浪潮| 18岁日韩内射颜射午夜久久成人| 久久夜色tv网站| 亚洲级αV无码毛片久久精品 | 久久93精品国产91久久综合| 中文字幕精品久久| 青青热久久综合网伊人| 中文无码久久精品| 久久亚洲精品无码观看不卡| 久久国产欧美日韩精品| 亚洲人成无码网站久久99热国产| 国产精品美女久久久久久2018| 思思久久精品在热线热| 久久亚洲天堂| 狠狠精品干练久久久无码中文字幕| 99蜜桃臀久久久欧美精品网站| 九九热久久免费视频| 91精品国产高清久久久久久91| 国产成年无码久久久久毛片| 国产69精品久久久久久人妻精品| 日韩久久久久中文字幕人妻 | 亚洲精品久久久www| 精品欧美一区二区三区久久久| 久久99国产精品一区二区| 狠狠色丁香久久婷婷综合五月| 午夜精品久久久久久久久| 无码人妻久久一区二区三区免费丨 | 国产成人精品免费久久久久| 欧美va久久久噜噜噜久久| 亚洲综合伊人久久大杳蕉|