麻豆小视频在线观看_中文黄色一级片_久久久成人精品_成片免费观看视频大全_午夜精品久久久久久久99热浪潮_成人一区二区三区四区

首頁 > 學(xué)院 > 開發(fā)設(shè)計(jì) > 正文

可變參數(shù)函數(shù)的原理與實(shí)例

2019-11-14 09:01:59
字體:
供稿:網(wǎng)友

在C/C++中,我們經(jīng)常會(huì)用到可變參數(shù)的函數(shù)(比如PRintf/snprintf等),本篇筆記旨在講解編譯器借助va_start/va_arg/va_end這簇宏來實(shí)現(xiàn)可變參數(shù)函數(shù)的原理,并在文末給出簡(jiǎn)單的實(shí)例。

        備注:本文的分析適用于linux/Windows,其它操作系統(tǒng)平臺(tái)的可變參數(shù)函數(shù)的實(shí)現(xiàn)原理大體相似。

1. 基礎(chǔ)知識(shí)        如果想要真正理解可變參數(shù)函數(shù)背后的運(yùn)行機(jī)制,建議先理解兩部分基礎(chǔ)內(nèi)容:         1)函數(shù)調(diào)用棧         2)函數(shù)調(diào)用約定        關(guān)于這兩個(gè)基礎(chǔ)知識(shí)點(diǎn),我之前的筆記有詳細(xì)介紹,感興趣的童鞋可以移步這里:棧與函數(shù)調(diào)用慣例—上篇 和棧與函數(shù)調(diào)用慣例—下篇

2. 三個(gè)宏:va_start/va_arg/va_end        由man va_start可知,這簇宏定義在stdarg.h中,在我的測(cè)試機(jī)器上,該頭文件路徑為:/usr/lib/gcc/x86_64-redhat-Linux/3.4.5/include/stdarg.h,在gcc源碼中,其路徑為:gcc/include/stdarg.h。        在stdarg.h中,宏定義的相關(guān)代碼如下:

[cpp] view plain copy#define va_start(v,l)  __builtin_va_start(v,l)  #define va_end(v)      __builtin_va_end(v)  #define va_arg(v,l)    __builtin_va_arg(v,l)  #if !defined(__STRICT_ANSI__) || __STDC_VERSION__ + 0 >= 199900L  #define va_copy(d,s)    __builtin_va_copy(d,s)  #endif  #define __va_copy(d,s)  __builtin_va_copy(d,s)          其中,前3行就是我們所關(guān)心的va_start & var_arg & var_end的定義(至于va_copy,man中有所提及,但通常不會(huì)用到,想了解的同學(xué)可man查看之)。可見,gcc將它們定義為一組builtin函數(shù)。        關(guān)于這組builtin函數(shù)的實(shí)現(xiàn)代碼,我曾試圖在gcc源碼中沿著調(diào)用路徑往下探索,無奈gcc為實(shí)現(xiàn)這組builtin函數(shù)引入了很多自定義的數(shù)據(jù)結(jié)構(gòu)和宏,對(duì)非編譯器研究者的我來說,實(shí)在有點(diǎn)晦澀,最終探索過程無疾而終。在這里,我列出目前跟蹤到的調(diào)用路徑,以便有興趣的童鞋能繼續(xù)探索下去或指出我的不足,先在此謝過。        __builtin_va_start()函數(shù)的調(diào)用路徑:[cpp] view plain copy// file: gcc/builtins.c  /* The "standard" implementation of va_start: just assign `nextarg' to the variable.  */  void std_expand_builtin_va_start (tree valist, rtx nextarg)                          {                                                                                   rtx va_r = expand_expr (valist, NULL_RTX, VOIDmode, EXPAND_WRITE);      convert_move (va_r, nextarg, 0);  // definition is in gcc/expr.c  }  // 上述代碼中調(diào)用了expand_expr()來展開表達(dá)式,我猜測(cè)該函數(shù)調(diào)用完后,va_list指向了可變參數(shù)list前的最后一個(gè)已知類型參數(shù)  //  file: gcc/expr.h  /* Generate code for computing expression EXP.     An rtx for the computed value is returned.  The value is never null.     In the case of a void EXP, const0_rtx is returned.   */  static inline rtx expand_expr (tree exp, rtx target, enum machine_mode mode,enum expand_modifier modifier)  {     return expand_expr_real (exp, target, mode, modifier, NULL);  }  

3. Windows系統(tǒng)VS內(nèi)置編譯器對(duì)va_start/va_arg/va_end的實(shí)現(xiàn)        如前所述,我沒能在gcc源碼中找出va_startva_arg/va_end這3個(gè)宏的實(shí)現(xiàn)代碼(⊙﹏⊙b汗),所幸的是,Windows平臺(tái)VS2008集成的編譯器中,對(duì)這三個(gè)函數(shù)有很明確的實(shí)現(xiàn)代碼,摘出如下。[cpp] view plain copy/* file path: Microsoft Visual Studio 9.0/VC/include/stdarg.h */  #include <vadefs.h>    #define va_start _crt_va_start  #define va_arg _crt_va_arg  #define va_end _crt_va_end          可見,Windows系統(tǒng)下,仍然將va_start/va_arg/va_end定義為一組宏。他們對(duì)應(yīng)的實(shí)現(xiàn)在vadefs.h中:[cpp] view plain copy/* file path: Microsoft Visual Studio 9.0/VC/include/vadefs.h */  #ifdef  __cplusplus  #define _ADDRESSOF(v)   ( &reinterpret_cast<const char &>(v) )  #else  #define _ADDRESSOF(v)   ( &(v) )  #endif    #define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )    #define _crt_va_start(ap,v)  ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )  #define _crt_va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )  #define _crt_va_end(ap)      ( ap = (va_list)0 )          備注:在VS2008提供的vadefs.h文件中,定義了若干組宏以支持不同的操作系統(tǒng)平臺(tái),上面摘出的代碼片段是針對(duì)IA x86_32的實(shí)現(xiàn)。        下面對(duì)上面的代碼做個(gè)解釋:         a. 宏_ADDRESSOF(v)作用:取參數(shù)v的地址。         b. 宏_INTSIZEOF(n)作用:返回參數(shù)n的size并保證4字節(jié)對(duì)齊(32-bits平臺(tái))。這個(gè)宏應(yīng)用了一個(gè)小技巧來實(shí)現(xiàn)字節(jié)對(duì)齊:~(sizeof(int) - 1)的值對(duì)應(yīng)的2進(jìn)制值的低k位一定是0,其中sizeof(int) = 2^k,因此,在IA x86_32下,k=2。理解了這一點(diǎn),那么(sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1)的作用就很直觀了,它保證了sizeof(n)的值按sizeof(int)的值做對(duì)齊,例如在32-bits平臺(tái)下,就是按4字節(jié)對(duì)齊;在64-bits平臺(tái)下,按8字節(jié)對(duì)齊。至于為什么要保證對(duì)齊,與編譯器的底層實(shí)現(xiàn)有關(guān),這里不再展開。         c. _crt_va_start(ap,v)作用:通過v的內(nèi)存地址來計(jì)算ap的起始地址,其中,v是可變參數(shù)函數(shù)的參數(shù)中,最后一個(gè)類型已知的參數(shù),執(zhí)行的結(jié)果是ap指向可變參數(shù)列表的第1個(gè)參數(shù)。以int snprintf(char *str, size_t size, const char *format, ...)為例,其函數(shù)參數(shù)列表中最后一個(gè)已知類型的參數(shù)是const char *format,因此,參數(shù)format對(duì)應(yīng)的就是_crt_va_start(ap, v)中的v, 而ap則指向傳入的第1個(gè)可變參數(shù)。        特別需要理解的是:為什么ap = address(v) + sizeof(v),這與函數(shù)棧從高地址向低地址的增長(zhǎng)方向 及函數(shù)調(diào)用時(shí)參數(shù)從右向左的壓棧順序有關(guān),這里默認(rèn)大家已經(jīng)搞清楚了這些基礎(chǔ)知識(shí),不再展開詳述。         d. _crt_va_arg(ap,t)作用:更新指針ap后,取類型為t的變量的值并返回該值。         e. _crt_va_end(ap)作用:指針ap置0,防止野指針。        概括來說,可變參數(shù)函數(shù)的實(shí)現(xiàn)原理是:         1)根據(jù)函數(shù)參數(shù)列表中最后一個(gè)已知類型的參數(shù)地址,得到可變參數(shù)列表的第一個(gè)可變參數(shù)         2)根據(jù)程序員指定的每個(gè)可變參數(shù)的類型,通過地址及參數(shù)類型的size獲取該參數(shù)值         3)遍歷,直到訪問完所有的可變參數(shù)        從上面的實(shí)現(xiàn)過程可以注意到,可變參數(shù)的函數(shù)實(shí)現(xiàn)嚴(yán)重依賴于函數(shù)棧及函數(shù)調(diào)用約定(主要是參數(shù)壓棧順序),同時(shí),依賴于程序員指定的可變參數(shù)類型。因此,若指定的參數(shù)類型與實(shí)際提供的參數(shù)類型不符時(shí),程序出core簡(jiǎn)直就是一定的。

4. 程序?qū)嵗?/strong>        經(jīng)過上面對(duì)可變參數(shù)函數(shù)實(shí)現(xiàn)機(jī)制的分析,很容易實(shí)現(xiàn)一個(gè)帶可變參數(shù)的函數(shù)。程序?qū)嵗缦拢?/p>[cpp] view plain copy#include <stdio.h>  #include <stdarg.h>    void foo(char *fmt, ...)   {      va_list ap;      int d;      char c, *p, *s;        va_start(ap, fmt);      while (*fmt)       {          if('%' == *fmt) {              switch(*(++fmt)) {                  case 's': /* string */                      s = va_arg(ap, char *);                      printf("%s", s);                      break;                  case 'd': /* int */                      d = va_arg(ap, int);                      printf("%d", d);                      break;                  case 'c': /* char */                      /* need a cast here since va_arg only takes fully promoted types */                      c = (char) va_arg(ap, int);                      printf("%c", c);                      break;                  default:                      c = *fmt;                      printf("%c", c);              }  // end of switch          }            else {              c = *fmt;              printf("%c", c);          }          ++fmt;      }      va_end(ap);  }    int main(int argc, char * argv[])  {      foo("sdccds%%, string=%s, int=%d, char=%c/n", "hello world", 211, 'k');      return 0;  }  

        上面的代碼很簡(jiǎn)單,旨在拋磚引玉,只要真正搞清楚了可變參數(shù)函數(shù)的原理,相信各位會(huì)寫出更加復(fù)雜精細(xì)的可變參函數(shù)。         ^_^

【參考資料】1. linux man : va_start2. wikipedia - x86 calling conventions3. VS2008頭文件:stdarg.h和vadefs.h的源碼

================== EOF =================


發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 久色亚洲 | 久久精品九九 | 久久久久夜色精品国产老牛91 | 国产一级性生活视频 | 久久久久久久一区二区 | 黄色网址在线视频 | 男男羞羞视频网站国产 | 一区二区三区四区视频在线观看 | 亚洲一级片免费观看 | 激情小说色 | 日韩毛片一区二区三区 | 国产成人av在线 | 国产1区2区3区中文字幕 | 免费国产一级淫片 | 蜜桃视频在线观看免费 | 91精品国产乱码久久桃 | 中文区永久区 | 国产成人av在线 | 最新av免费网址 | 妇子乱av一区二区三区 | 国产在线色 | 中文字幕天堂在线 | 在线免费观看毛片 | 2019中文字幕在线播放 | 韩国美女一区 | 久久亚洲精品国产一区 | 国产大片中文字幕在线观看 | 中国杭州少妇xxxx做受 | 久久综合福利 | 成人一级黄色片 | 久久久精品精品 | 中文字幕极速在线观看 | 91一级毛片 | omofun 动漫在线观看 | 欧美日韩亚州综合 | 亚洲一区二区三区在线看 | 九九精品在线播放 | 91精品国产91久久久久久丝袜 | 一区二区三区视频在线播放 | 今井夏帆av一区二区 | 3级毛片 |