再次理解C語言的變參

2021-08-31 06:29:16 字數 3982 閱讀 6192

實在是令我很鬱悶的事啊。

去年用了兩天的時間惡補了一下變參,今天看到變參。發現頭腦一篇空白,啥都不知道了。

古人有云:溫故而知新。今日我就在看一遍,做個筆記了。

在 c 語言中,函式引數的 傳遞方式有值傳和址傳 . 值傳是把實參的乙個專用的、臨時的複製值給被調函式中相應的形參 被呼叫函式使用、修改這個傳來的複製值,不會影響實參的值 . 址傳則 是把變數 ( 實參 ) 的位址傳給被調函式 . 被調函式通過這個位址找到該變數的存放位置,直接對該位址中存放 的變數的內容進行訪問操作 . 無論是值傳還是址傳,都要求實參的數目及型別與形參 要完全一致。

但是在c語言中,在形參表中可以不明確指定傳遞引數的個數和型別。printf就是乙個最好的例子了。這也就是今天要討論的主要話題--變參函式

下面我們通過對可變長引數函式的理解和設計, 加深 c 語言函式設計的思想方法。

在標準檔案 stdarg.h 中包含帶引數的巨集定義:

typedef __builtin_va_list __gnuc_va_list;

typedef __gnuc_va_list va_list

#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)

__builtin_va_list等帶有__builtin都是編譯器所能識別的函式,類似於關鍵字,這裡就不深究了。

下面舉例一下可變引數的使用注意事項:

1,可變長引數函式規定格式 。

firstfix,…,lastfix表示函式引數列表中的第乙個和最後乙個固定引數,該引數列表至少要有乙個固定引數,其作用是為了給變參函式確定列表中引數的個數和引數的型別。

2,指標型別va_list用來說明乙個變數ap(argument pointer -- 可變引數指標),此變數將依次引用可變引數列表中用省略號「...」代替的每乙個引數。即指向將要操作的變參。

3,巨集va_start(ap,lastfix)是為了初始化變參指標ap,以指向可變引數列表中未命名的第乙個引數,即指向lastfix後的第乙個變參。它必須在指標使用之前呼叫一次該巨集,引數列表中至少有乙個未命名的可變引數。從巨集定義可知其正確性。

4,巨集va_arg(ap, type)呼叫,將ap指向下乙個可變引數,而ap的型別由type確定,type資料型別不使用float型別。呼叫後將新的變參指向乙個工作變參,如iap=va_start(ap,int)呼叫。

5,巨集va_end(ap)功能是完成清除變數ap的作用,表明程式以後不再使用,若該指標變數需再使用,必須重新呼叫巨集va_start以啟動該變數。

下面舉兩個列子,乙個使用可變引數實現,乙個使用常規方式實現:

#include #include int add_va(int num, int first, ...) 

va_end(ap);//清除變數ap

return ret;

}int add_normal(int num, ...)

int main(int argc, char **argv)

得出結論:

可變引數在編譯器中的處理

va_start,va_arg,va_end 是在 stdarg.h 中 被定義成巨集的 , 由於 1) 硬體平台的不同 2) 編譯器的不同 , 所以定義的巨集也有所不同 , 下面以 vc++ 中 stdarg.h 裡 x86 平台的巨集定義摘錄如下 :

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) 中注意

((sizeof

(n)+

sizeof

(int)-

1)&~(

sizeof

(int) -

1) ), 其實這段表示式主要是為了位元組對齊作用,這裡是使用int值來確定對齊的方式。很好很強大。

圖 (1) 是函式的引數在堆疊中的分布位置 。執行

va_start(ap, v) 以後 ,ap 指向第一 個可變引數在堆疊的位址

高位址|-----------------------------| 

|函式返回位址 |

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

|. |

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

|第n個引數(第乙個可變引數) |

|-----------------------------|<--va_start後ap指向

|第n-1個引數(最後乙個固定引數)|

低位址|-----------------------------|<-- &v

圖( 1 )

然後,使用va_arg() 取得型別 t 的可變引數值 。舉例

va_arg 取 int 型的返回值 :

j= ( *(int*)((ap += _intsizeof(int))-_intsizeof(int)) )。

1,ap+=sizeof(int),ap指向了下乙個引數的位址

2,減去sizeof(int),相當於又返回到了前乙個引數,不過這一步沒有賦值給ap。

3,  將表示式的值強轉成int型別。

4,返回第n(第乙個可變引數)的值,並且賦值給變數j。

高位址|-----------------------------| 

|函式返回位址 |

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

|. |

|-----------------------------|<--va_arg後ap指向

|第n個引數(第乙個可變引數) |

|-----------------------------|<--va_start後ap指向

|第n-1個引數(最後乙個固定引數)|

低位址|-----------------------------|<-- &v

圖( 2 )

最後要說的是 va_end 巨集的意思 ,x86 平 臺定義為 ap=(char*)0; 使 ap 不再指向堆疊 , 而是跟 null 一樣 . 有些直接定義為 ((void*)0), 這樣編譯器不會為 va_end 產生** , 例如 gcc 在 linux 的 x86 平台就是這樣定義的 .

note:

由於引數的位址用於 va_start 巨集 , 所以引數不能宣告為暫存器變數或作為函式或陣列型別 。

不同的作業系統和硬體平台的定義有些不同 , 但原理卻是相似的。

接下來自己寫了乙個printf:

#include #include #include void printf_diy(char *fmt,...) 

else

va_arg(arg,int);

}++fmt;

} while (*fmt != '\0');

va_end(arg);

return;

}int main(int argc, char **argv)

輸出的結果是:

i = 1234

j = 5678

f = -2.000000

這裡不明白f會輸出的是怎麼會是「 -2.000000」 。今天就寫到這裡了,該問題以後解決。哈哈!

C語言變參使用

c語言中有很多變參的使用,例如printf 的原型是int printf const char fmt,那麼c語言是如何解析和處理這些變參的呢?下面進行簡單的總結 c語言中定義了下面的一些巨集,專門用來處理變參 va start va list ap,char fmt va arg va list ...

C語言(變參函式)

c語言雖然沒有c 的函式過載特性,但也可以實現變參,但要保證第乙個引數資訊的完整性。拓展 定義變參函式時,第乙個引數一般是字串,攜帶後續變參的型別和數量資訊,變參使用三點來表示,如 void sumup const char info,再使用va list va start va arg 和va e...

C語言變參,記錄

由於在 c語言中沒有函式過載 解決不定數目函式引數問題變得比較麻煩 即使採用 c 如果引數個數不能確定 也很難採用函式過載 對這種情況 有些人採用指標引數來解決問題.uhmm 用到的變參地方 用的原因 1,sql 語言必須用常量2,sql語言格式不相同,引數個數不相同使用a dyw mysql nu...