stdarg.h+vfprintf+abort

來源:互聯網
上載者:User

http://baike.baidu.com/view/1340814.htm

可變參數

  在C/C++函數中使用可變參數

  作者轉自:http://foggy-elves.blog.sohu.com/

  下面介紹在C/C++裡面使用的可變參數函數(函數的參量不知道有多少個)

  先說明可變參數是什麼,先回顧一下C++裡面的函數重載,如果重複給出如下聲明:

  int func();

  int func(int);

  int func(float);

  int func(int, int);

  ...

  這樣在調用相同的函數名 func 的時候,編譯器會自動識別入參列表的格式,從而調用相對應的函數體。

  但這樣的方法畢竟有限,試想一下我們假如想定義一個函數,我們在調用之前(在運行期之前)根本不知道我到底要調用幾個參數,並且不知道這些參數是個什麼類型,例如我們想定義一個函數:

  int max(int n, ...);

  用來返回一串隨意長度輸入參數的最大值,例如調用

  max(3, 10, 20, 30)的時候,可以返回(n=3)個數 10,20,30 的最大值30。

  並且還可以接受任意個參數的輸入,例如:

  max(6, 20, 40, 10, 50, 30, 40)也應該是被接受的,返回最大值50。

  這怎麼達到呢?

  其實這樣的例子我們肯定見過,最典型的就是 printf 函數,可以看 printf 函數的原形:

  int printf(char*, ...);

編輯本段標準
C 庫 <stdarg.h>

  它接受一個格式字串,並且後面跟隨任意指定的參數,根據實際需要而確定入參的個數。

  實際上它的實現要依賴於一個標準 C 庫 <stdarg.h>,standard argument(標準參數) 的意思。下面先稍為介紹一下 <stdarg.h>,或者在 C++ 中的 <cstdarg> 的功效:

  這實際上是一組初始化和調用可變參數的宏,下面先介紹一下可變參數表的調用形式以及原理:

  首先是參數的記憶體存放格式:參數存放在記憶體的堆棧段中,在執行函數的時候,從最後一個開始入棧。因此棧底高地址,棧頂低地址,舉個例子如下:

  void func(int x, float y, char z);

  那麼,調用函數的時候,實參 char z 先進棧,然後是 float y,最後是 int x,因此在記憶體中變數的存放次序是
x->y->z,因此,從理論上說,我們只要探測到任意一個變數的地址,並且知道其他變數的類型,通過指標移位元運算,則總可以順藤摸瓜找到其他的輸入變數。

  然後是可變入參表格式,省略的參數用 ... 代替,但必須注意:

  1. 只能有一個 ... 並且它必須是最後一個參數;

  2. 不要只用一個 ... 作為所有的參數,因為從後面可以知道,這樣你無法確定入參表的地址(起始)

  舉個例子,聲明函數如下:

  void func(int x, int y, ...);

  然後調用:func(3, 5, 'c', 2.1f, 6);

  於是在調用參數的時候,編譯器則不會檢查實際輸入的是什麼參數,只管把所有參數按照上面描述的方法,變成實參堆放在記憶體中,在本例中,記憶體中依次存放 x=3, y=5, 'c', 2.1f, 6

  但是有一個需要注意的地方,這些東西只是緊挨著堆放在記憶體中,於是想要正確調用這些參數,必須知道他們確切的類型(明),並且我們也關心這個參數表實際的長度,然而不幸的是,這些我們無從得知。因此,這個解決辦法決不是高明的,從某種程度上說,這甚至是一個嚴重的漏洞。因此,C++ 很不提倡去使用它。

  不過缺點歸缺點,萬不得已的時候我們還是得用,但是我們對裡面輸入變數的時候,應該對入參的類型有一個清醒的認識,否則這樣的操作是很危險的。

  下面是 <stdarg.h> 對上面這一個思路的實現,裡面重要的幾個宏定義如下:

  typedef char* va_list;

  void va_start ( va_list ap, prev_param ); /* ANSI version */

  type va_arg ( va_list ap, type );//這裡可以確定類型

  void va_end ( va_list ap );

  其中,va_list 是一個字元指標,可以理解為指向當前參數的一個指標,取參必須通過這個指標進行。

編輯本段使用<stdarg.h>步驟<Step 1>

  在調用參數表之前,應該定義一個 va_list 類型的變數,以供後用(下面假設這個 va_list 類型變數被定義為ap);

<Step 2>

  然後應該對 ap 進行初始化,讓它指向可變參數表裡面的第一個參數,這是通過 va_start 來實現的,第一個參數是 ap 本身,第二個參數是在變參表前面緊挨著的一個變數;

<Step 3>

  然後是擷取參數,調用 va_arg,它的第一個參數是 ap,第二個參數是要擷取的參數的指定類型,然後返回這個指定類型的值,並且把 ap 的位置指向變參表的下一個變數位置;

<Step 4>

  擷取所有的參數之後,我們有必要將這個 ap 指標關掉,以免發生危險(結尾也十分關鍵),方法是調用 va_end,他是輸入的參數 ap 置為 NULL,應該養成擷取完參數表之後關閉指標的習慣。

例子

  例如開始的例子 int max(int n, ...); 其函數內部應該如此實現:

  int max(int n, ...) { // 定參 n 表示後面變參數量,定界用,輸入時切勿搞錯

  va_list ap; // 定義一個 va_list 指標來訪問參數表

  va_start(ap, n); // 初始化 ap,讓它指向第一個變參

  int maximum = -0x7FFFFFFF; // 這是一個最小的整數

  int temp;

  for(int i = 0; i < n; i++) {

  temp = va_arg(ap, int); // 擷取一個 int 型參數,並且 ap 指向下一個參數

  if(maximum < temp) maximum = temp;

  }

  va_end(ap); // 善後工作,關閉 ap

  return maximum ;

  }

  // 在主函數中測試 max 函數的行為(C++ 格式)

  int main() {

  cout << max(3, 10, 20, 30) << endl;

  cout << max(6, 20, 40, 10, 50, 30, 40) << endl;

  }

編輯本段存在不足

  基本用法闡述至此,可以看到,這個方法存在兩處極嚴重的漏洞:

其一,

  輸入參數的類型隨意性,使得參數很容易以一個不正確的類型擷取一個值(譬如輸入一個float,卻以int型去擷取他),這樣做會出現莫名其妙的運行結果;

其二,

  變參表的大小並不能在編譯時間擷取,這樣就存在一個訪問越界的可能性,導致後果嚴重的 RUNTIME ERROR。

  另外,<stdarg.h> 的內部實現形式在這處不再加說明,如果有需要可以參考下面的兩個串連(感謝他們的作者)。[1-2]

編輯本段建議

  作為建議,在 C++ 環境中盡量不要使用這種方法,如有需要,盡量先考慮使用類或者重載來代替,這樣可以很好地彌補這種方法的漏洞。

  全文完感謝讀者,ELF原創,轉載請註明出處

  bitou補充:

  這裡面有一個例子,求參數的平均值,這些參數是可變參數,現在將函數奉上:

  #include <stdarg.h>

  float average(int n_value,...)//可變參數函數

  {

  va_list var_arg;//va_list類型變數用於訪問參數列表中未定義的部分

  int count;

  float sum = 0;

  va_start(var_arg,n_value);//va_start宏將var_arg指定為可變參數部分的第一個參數

  for(count=0; count<n_value;count +=1)

  {

  sum += va_arg(var_arg,int);//va_arg返回var_arg的值,並指向參數列表中的下一個參數

  }

  va_end(var_arg);//訪問完最後一個可變參數之後,調用va_end宏終止使用可變參數

  return sum/n_value;

  }

http://baike.baidu.com/view/1081188.htm

vfprintf  函數名: vfprintf

  功 能: 格式化的資料輸出到指定的資料流中

  用 法: int vfprintf(FILE *stream, char *format, va_list param);

  函數說明:vfprintf()會根據參數format字串來轉換並格式化資料,然後將結果輸出到參數stream指定的檔案中,直到出現字串結束(‘\0’)為止。關於參數format字串的格式請參 考printf()。

  傳回值:成功則返回實際輸出的字元數,失敗則返回-1,錯誤原因存於errno中。

  程式例:

  #include

  #include

  #include

  FILE *fp;

  int vfpf(char *fmt, ...)

  {

  va_list argptr;

  int cnt;

  va_start(argptr, fmt);

  cnt = vfprintf(fp, fmt, argptr);

  va_end(argptr);

  return(cnt);

  }

  int main(void)

  {

  int inumber = 30;

  float fnumber = 90.0;

  char string[4] = "abc";

  fp = tmpfile();

  if (fp == NULL)

  {

  perror("tmpfile() call");

  exit(1);

  }

  vfpf("%d %f %s", inumber, fnumber, string);

  rewind(fp);

  fscanf(fp,"%d %f %s", &inumber, &fnumber, string);

  printf("%d %f %s\n", inumber, fnumber, string);

  fclose(fp);

  return 0;

  }

http://baike.baidu.com/view/531918.htm

函數簡介

  函數名: abort

  標頭檔:#include <stdlib.h>

  功 能: 異常終止一個進程。中止當前的過程,返回一個錯誤碼。錯誤碼的預設值是3

  中止一個程式異常終止的過程。

  該函數產生SIGABRT訊號,預設情況下導致程式終止不成功的終止錯誤碼返回到主機環境。

  自動或靜態儲存期間的對象,而無需調用任何atexit函數,解構函式不執行程式終止。函數永遠不會返回到其調用者。

  用 法: void abort(void);

編輯本段舉例

  程式例:

  #include <stdio.h>

  #include <stdlib.h>

  int main(void)

  {

  printf("Calling abort()\n");

  abort();

  return 0; /* This is never reached */

  }


聯繫我們

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