先寫乙個不定參函式 ,返回值為 不定參引數之和,ps(巨集由自己定義的,與庫里的不一樣)
#include
#define va_list void* //形參列表元素的指標
#define va_arg(arg, type) *(type*)arg; arg = (char*)arg + sizeof(type); //取出不定參的第乙個值
//指標進行加1
#define va_start(arg, start) arg = (va_list)(((char*)&(start)) + sizeof(start)) //取出引數列表中第乙個固定引數,
//然後移動指標到不定參的第乙個位置
int sum(int count, ...)
return result;
}int main(int argc, char* ar**)
400600
press any key to continue
下面深度剖析可變參函式的實現:可變參函式:
可變參函式是採用c語言程式設計的時候,函式中形式引數的數目通常是確定的,在呼叫時要依次給出與形式引數對應的所有實際引數。
典型的例子:
函式printf()、scanf()和系統呼叫execl()等
實現原理:
c編譯器通常提供了一系列處理這種情況的巨集,以遮蔽不同的硬體平台造成的差異,增加程式的可移植性。這些巨集包括va—start、va—arg和va—end等。
實現難點:
實現可變引數的要點就是想辦法取得每個引數的位址,取得位址的辦法由以下幾個因素決定:
①函式棧的生長方向
②引數的入棧順序
③cpu的對齊方式
④記憶體位址的表達方式
對這三個巨集進行解析:
va—start使argp指向第乙個可選引數。
va—arg返回引數列表中的當前引數並使argp指向引數列表中的下乙個引數。
va—end把argp指標清為null。函式體內可以多次遍歷這些引數,但是都必須以va—start開始,並以va—end結尾
va_list:用來儲存巨集va_start、va_arg和va_end所需資訊的一種型別。為了訪問變長引數列表中的引數,必須宣告
va_list型別的乙個物件 定義: typedef char * va_list;
va_start:訪問變長引數列表中的引數之前使用的巨集,它初始化用va_list宣告的物件,初始化結果供巨集va_arg和
va_end使用;
va_arg: 展開成乙個表示式的巨集,該表示式具有變長引數列表中下乙個引數的值和型別。每次呼叫va_arg都會修改
用va_list宣告的物件,從而使該物件指向引數列表中的下乙個引數;
va_end:該巨集使程式能夠從變長引數列表用巨集va_start引用的函式中正常返回。
va在這裡是variable-argument(可變引數)的意思.
⑴由於在程式中將用到以下這些巨集:
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中,所以用到可變引數的程式應該包含這個標頭檔案.
⑵函式裡首先定義乙個va_list型的變數,這裡是arg_ptr,這個變數是儲存引數位址的指標.因為得到引數的位址之後,再結合引數的型別,才能得到引數的值。
⑶然後用va_start巨集初始化⑵中定義的變數arg_ptr,這個巨集的第二個引數是可變引數列表的前乙個引數,即最後乙個固定引數.
⑷然後依次用va_arg巨集使arg_ptr返回可變引數的位址,得到這個位址之後,結合引數的型別,就可以得到引數的值。
⑸設定結束條件,這裡的條件就是判斷引數值是否為-1。注意被調的函式在呼叫時是不知道可變引數的正確數目的,程式設計師必須自己在**中指明結束條件。至於為什麼它不會知道引數的數目,讀者在看完這幾個巨集的內部實現機制後,自然就會明白。
函式棧的實現過程:
⑴在intel+windows的機器上,函式棧的方向是向下的,棧頂指標的記憶體位址低於棧底指標,所以先進棧的資料是存放在記憶體的高位址處。
(2)在vc等絕大多數c編譯器中,預設情況下,引數進棧的順序是由右向左的,因此,引數進棧以後的記憶體模型如下圖所示:最後乙個固定引數的位址位於第乙個可變引數之下,並且是連續儲存的。|————————–|
最後乙個可變引數
->高記憶體位址處
第n個可變引數
->va_arg(arg_ptr,int)後arg_ptr所指的地方,
即第n個可變引數的位址。
第乙個可變引數
->va_start(arg_ptr,start)後arg_ptr所指的地方
即第乙個可變引數的位址
最後乙個固定引數
-> start的起始位址
……………..
-> 低記憶體位址處
(4) va_arg():有了va_start的良好基礎,我們取得了第乙個可變引數的位址,在va_arg()裡的任務就是根據指定的引數型別取得本引數的值,並且把指標調到下乙個引數的起始位址。
因此,現在再來看va_arg()的實現就應該心中有數了:
#define va_arg(ap,t) ( (t )((ap += _intsizeof(t)) - _intsizeof(t)) )
這個巨集做了兩個事情,
①用使用者輸入的型別名對引數位址進行強制型別轉換,得到使用者所需要的值
②計算出本引數的實際大小,將指標調到本引數的結尾,也就是下乙個引數的首位址,以便後續處理。
(四)小結:
1、標準c庫的中的三個巨集的作用只是用來確定可變引數列表中每個引數的記憶體位址,編譯器是不知道引數的實際數目的。
2、在實際應用的**中,程式設計師必須自己考慮確定引數數目的辦法,如
⑴在固定引數中設標誌– printf函式就是用這個辦法。後面也有例子。
⑵在預先設定乙個特殊的結束標記,就是說多輸入乙個可變引數,呼叫時要將最後乙個可變引數的值設定成這個特殊的值,在函式體中根據這個值判斷是否達到引數的結尾。本文前面的**就是採用這個辦法.
無論採用哪種辦法,程式設計師都應該在文件中告訴呼叫者自己的約定。
3、實現可變引數的要點就是想辦法取得每個引數的位址,取得位址的辦法由以下幾個因素決定:
①函式棧的生長方向
②引數的入棧順序
③cpu的對齊方式
④記憶體位址的表達方式
結合源**,我們可以看出va_list的實現是由④決定的,_intsizeof(n)的引入則是由③決定的,他和①②又一起決定了va_start的實現,最後va_end的存在則是良好程式設計風格的體現,將不再使用的指標設為null,這樣可以防止以後的誤操作。
4、取得位址後,再結合引數的型別,程式設計師就可以正確的處理引數了。理解了以上要點,相信稍有經驗的讀者就可以寫出適合於自己機器的實現來。下面就是乙個例子 實現 類似於
myprintf()
#include "stdio.h"
#include "stdlib.h"
void myprintf(char* fmt, ...) //乙個簡單的類似於printf的實現,//引數必須都是int 型別
else
parg += sizeof(int); //等價於原來的va_arg
} ++fmt;
}while (*fmt != '\0');
parg = null; //等價於va_end
return;
} int main(int argc, char* ar**)
可變參函式
int add int x,int main int add int x,int sum 0 char point char x for int i 0 iint add int x,可變參函式原型,該函式中帶有識別符號的引數 x記錄的是引數的個數,後面的數字是需要求和的數。x的作用是為了標誌出加數...
可變參函式
採用c語言程式設計的時候,函式中形式引數的數目通常是確定的,在呼叫時要依次給出與形式引數對應的所有實際引數。但在某些情況下希望函式的引數個數可以根據需要確定。例如 printf const char format,c語言可變引數通過三個巨集 va start va end va arg 和乙個型別 ...
可變參函式(my printf可變參函式的實現)
可變參函式 其引數列表的引數型別與個數可變,採用ansi標準形式時,引數個數可變的函式的原型宣告是 type funcname type para1,type para2,至少需要乙個普通的形式引數,後面的省略號不表示省略,而是函式原型的一部分,為引數佔位符,type是函式返回值和形式引數的型別 可...