Windows下C/C++可變參數宏實現技巧

WindowsC/C++可變參數宏實現技巧 linux

在開發過程當中,有不少階段,每一個階段可能會注重不一樣的重點,咱們可能會在不一樣階段讓程序輸出或者打印不一樣的信息以反應運行的狀況,因此咱們必須分階段的使得程序輸出咱們在每一個階段所要關心的信息,甚至在最後讓程序再也不輸出信息。這就要用到了宏定義! 編程

      咱們知道,在linux下很方便的就能實現可變參數宏的定義, windows

好比: 編輯器

#define myprint(fmt, a...)    printf("%s,%s(),%d:" fmt "/n", __FILE__,__FUNCTION__,__LINE__, ##a)就定義了本身的輸出宏,當沒必要再輸出這些多是調式,跟蹤,斷言,日誌...的信息時,能夠再定義宏爲空: ide

#define myprintf(fmt,a...) 函數

這樣,從新編譯後,這些宏引用的地方將所有沒有語句,從而省去這些開銷。 spa

可是,在windows下,通常咱們採用的VC6.0,VS2003,VS2005,VS2008(待定)編輯器中自帶的C/C++編譯器並不支持變參宏的定義,gcc編譯器支持,聽說最新版本的C99也支持。 操作系統

能夠在windows下這樣定義宏: debug

#define myprint printf 設計

但 是,當後期不想再要宏輸出了,只能定義 #define myprint爲空,在那些有宏調用的代碼區會留下相似 ("DEBUG:>> %d,%s,%f",idx,"weide001",99.001);這樣的語句,它應該會被程序運算一次,應該會像函數參數那樣被壓棧,出棧一次,從而 增長了程序的運行開銷,不是一個上策。

因此,在windows下須要變通一下,如下四種方式能夠做爲從此windows下定義可變參宏定義的參考:

      1)引用系統變參函數:

#include <stdarg.h>

 

#ifdef _WIN32

  #define vsnprinf _vsnprintf

  #define vsprinf  _vsprintf

#endif

 

int my_print(const char* file,cosnt char* fun,const char* line,const char *fmt, ...)

{

  int t = 0;

  char out[1024]="";

  va_list ap;

 

  /*
  Test Env:
    gcc version 3.4.5 20051201 (Red Hat 3.4.5-2)
  Result:
  
使用
vsprintf時:
    
fmt的長度大於1024時出現: 段錯誤
    
fmt的長度小於1024時出現: 正常
  
使用vsnprintf時:
    
fmt的長度大於1024時出現: 多餘的字符不保存到out
    
fmt的長度小於1024時出現: 正常
  vsnprintf
的返回值同snprintf很類似

  ---------------------------------------------------
  Test Env:
    Microsoft Windows XP [
版本 5.1.2600]
    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 12.00.8168 for 80x86
  Result:
  
使用_vsprintf時:
    
fmt的長度大於1024時出現: 段錯誤
    
fmt的長度小於1024時出現: 正常
  
使用_vsnprintf時:
    
fmt的長度大於1024時出現: 多餘的字符不保存到out
    
fmt的長度小於1024時出現: 正常
  _vsnprintf
的返回值同_snprintf很類似
   */


  va_start(ap, fmt);

  t = vsnprintf(out, 1024, fmt, ap);

  va_end(ap);

 

  printf("%s,%s(),%d: %s/n", file,fun,line,out);

 

  return (t);

}

 

#define myprint(str)  my_print(__FILE__,__FUNCTION__,__LINE__,str)

只能輸出一個字符串參數。啥也別說了,這個確定很爛,這裏主要是記住這個變參函數的實現方式。

 

2)新的C99規範支持了可變參數的宏,具體使用以下:

#include   <stdarg.h>

 

#define   myprint(fmt, ...)   printf(fmt,__VA_ARGS__)  

 

3)這個很雷人:(‘_’來代替‘,’,不然報錯或者警告實參太多或者實參個數不一致)

#define   _   , 

#define   mysprintf(geter, fmt, args)   sprintf(geter, fmt, args)  

 

{

   char str[128] = "";

   mysprintf(str, "name=%s age=%d weight=%f" _ "weide001" _ 23 _ 57.9);//調用很累,和以往的方式出入太大

}

 

4)使用##

#define   mysprintf(geter)   sprintf##geter 

 

{

  char str[128] = "";

  mysprintf(  (str,"name=%s age=%d weight=%f","weide001",23,57.9)  );//也有點雷人,調用時多出來了一層括弧

}

 

可變參數的宏裏的 ## 做用

GCC始終支持複雜的宏,它使用一種不一樣的語法從而可使你能夠給可變參數一個名字,如同其它參數同樣。例以下面的例子:

#define debug(format, args...) fprintf (stderr, format, args)

可是在debug可變參數爲0的時候,debug("hello /n"),編譯會出錯,採用這樣的方式:

#define debug(format, ...) fprintf (stderr, format, ##args)就能夠

##的用法,文中是這樣解釋的:「這裏,假如可變參數被忽略或爲空,‘##’操做將使預處理器(preprocessor)去除掉它前面的那個逗號。」」這句話不明白,是##的新功能,仍是原有鏈接的功能的應用?

其實,## 是粘連符

好比windows #define __Ttext L##text

就是在text前面加上了一個L

__T("abc")就成了 L"abc"

 

可變參數及可變參數宏的使用

咱們在C語言編程中會遇到一些參數個數可變的函數,例如printf()這個函數,這裏將介紹可變函數的寫法以及原理.

 

* 1. 可變參數的宏

通常在調試打印Debug 信息的時候, 須要可變參數的宏. C99開始可使編譯器標準支持可變參數宏(variadic macros), 另外GCC 也支持可變參數宏, 可是兩種在細節上可能存在區別.

 

1. __VA_ARGS__

__VA_ARGS__ "..." 傳遞給宏.
#define debug(format, ...) fprintf(stderr, fmt, __VA_ARGS__)

GCC中也支持這類表示, 可是在G++ 中不支持這個表示.

 

2. GCC 的複雜宏

GCC使用一種不一樣的語法從而可使你能夠給可變參數一個名字,如同其它參數同樣。

#define debug(format, args...) fprintf (stderr, format, args)

這和上面舉的那個定義的宏例子是徹底同樣的,可是這麼寫可讀性更強而且更容易進行描述。

 

3. ##__VA_ARGS__

上面兩個定義的宏, 若是出現debug("A Message") 的時候, 因爲宏展開後有個多餘的逗號, 因此將致使編譯錯誤. 爲了解決這個問題,CPP使用一個特殊的‘##’操做。

#define debug(format, ...) fprintf (stderr, format, ## __VA_ARGS__)

這裏,若是可變參數被忽略或爲空,‘##’操做將使預處理器(preprocessor)去除掉它前面的那個逗號。若是你在宏調用時,確實提供了一些可變參數,GNU CPP也會工做正常,它會把這些可變參數放到逗號的後面。

 

4. 其餘方法

一種流行的技巧是用一個單獨的用括弧括起來的的 "參數" 定義和調用宏, 參數在宏擴展的時候成爲相似 printf() 那樣的函數的整個參數列表。

#define DEBUG(args) (printf("DEBUG: "), printf(args))

 

* 2. 可變參數的函數

寫可變參數的C函數要在程序中用到如下這些宏:
void va_start( va_list arg_ptr, prev_param )
type va_arg( va_list arg_ptr, type )
void va_end( va_list arg_ptr )

va在這裏是variable-argument(可變參數)的意思,這些宏定義在stdarg.h.下面咱們寫一個簡單的可變參數的函數,該函數至少有一個整數參數,第二個參數也是整數,是可選的.函數只是打印這兩個參數的值.
void simple_va_fun(int i, ...)
{
   va_list arg_ptr;
   int j=0;
  
   va_start(arg_ptr, i);
   j=va_arg(arg_ptr, int);
   va_end(arg_ptr);
   printf("%d %d/n", i, j);
   return;
}

在程序中能夠這樣調用:
simple_va_fun(100);
simple_va_fun(100,200);

從這個函數的實現能夠看到,使用可變參數應該有如下步驟:
1)
首先在函數裏定義一個va_list型的變量,這裏是arg_ptr,這個變量是指向參數的指針.
2)
而後用va_start宏初始化變量arg_ptr,這個宏的第二個參數是第一個可變參數的前一個參數,是一個固定的參數.
3)
而後用va_arg返回可變的參數,並賦值給整數j. va_arg的第二個參數是你要返回的參數的類型,這裏是int.
4)
最後用va_end宏結束可變參數的獲取.而後你就能夠在函數裏使用第二個參數了.若是函數有多個可變參數的,依次調用va_arg獲取各個參數.

若是咱們用下面三種方法調用的話,都是合法的,但結果卻不同:
1)simple_va_fun(100);
結果是:100 -123456789(會變的值)
2)simple_va_fun(100,200);
結果是:100 200
3)simple_va_fun(100,200,300);
結果是:100 200

咱們看到第一種調用有錯誤,第二種調用正確,第三種調用盡管結果正確,但和咱們函數最初的設計有衝突.下面一節咱們探討出現這些結果的緣由和可變參數在編譯器中是如何處理的.
* 3.
可變參數函數原理

va_start,va_arg,va_end是在stdarg.h中被定義成宏的,因爲硬件平臺的不一樣,編譯器的不一樣,因此定義的宏也有所不一樣,下面以VC++stdarg.hx86平臺的宏定義摘錄以下:

typedef char * va_list;
#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 )

定義_INTSIZEOF(n)主要是爲了內存對齊,C語言的函數是從右向左壓入堆棧的(設數據進棧方向爲從高地址向低地址發展,即首先壓入的數據在高地址). 下圖是函數的參數在堆棧中的分佈位置:

低地址       |-----------------------------|<-- &v

                          |n-1個參數(最後一個固定參數)|

                          |-----------------------------|<--va_startap指向

                          |n個參數(第一個可變參數) |

                          |-----------------------------|

                          |....... |

                          |-----------------------------|
                          |
函數返回地址 |

高地址      |-----------------------------|

1. va_list 被定義爲char *
2. va_start
將地址ap定義爲 &v+_INTSIZEOF(v),&v是固定參數在堆棧的地址,因此va_start(ap, v)之後,ap指向第一個可變參數在堆棧的地址
3. va_arg
取得類型t的可變參數值,int型爲例,va_argint型的返回值:
   j= ( *(int*)((ap += _INTSIZEOF(int))-_INTSIZEOF(int)) );
4. va_end
使ap再也不指向堆棧,而是跟NULL同樣.這樣編譯器不會爲va_end產生代碼.

在不一樣的操做系統和硬件平臺的定義有些不一樣,但原理倒是類似的.

* 4. 小結

對於可變參數的函數,由於va_start, va_arg, va_end等定義成宏,因此它顯得很愚蠢,可變參數的類型和個數須要在該函數中由程序代碼控制;另外,編譯器對可變參數的函數的原型檢查不夠嚴格,對編程查錯不利.
因此咱們寫一個可變函數的C函數時,有利也有弊,因此在沒必要要的場合,無需用到可變參數.若是在C++,咱們應該利用C++的多態性來實現可變參數的功能,儘可能避免用C語言的方式來實現.

* 5. 附一些代碼

#define debug(format, ...) fprintf(stderr, fmt, __VA_ARGS__)
#define debug(format, args...) fprintf (stderr, format, args)
#define debug(format, ...) fprintf (stderr, format, ## __VA_ARGS__)

//
使用va... 實現
void debug(const char *fmt, ...) {     int nBuf;     char szBuffer[1024];     va_list args;     va_start(args, fmt);     nBuf = vsprintf(szBuffer, fmt, args) ;     assert(nBuf >= 0);     printf("QDOGC ERROR:%s/n",szBuffer);     va_end(args); }

相關文章
相關標籤/搜索