亚洲精品中文字幕无乱码_久久亚洲精品无码AV大片_最新国产免费Av网址_国产精品3级片

C語言

C語言中變參函數(shù)的實(shí)現(xiàn)細(xì)節(jié)

時(shí)間:2024-10-09 21:29:10 C語言 我要投稿
  • 相關(guān)推薦

C語言中變參函數(shù)的實(shí)現(xiàn)細(xì)節(jié)

  C語言的函數(shù)雖然不具備C++的多態(tài)性,但也可以接受參數(shù)不確定的情況,當(dāng)然,C語言中的變參函數(shù)實(shí)際在功能上是受限的,廢話不多講,下面來看看變參函數(shù)的邊邊角角的問題。以下僅供參考!

  討論之前我們來看一下最熟悉的變參函數(shù)printf的原型聲明:

  --------------------------------------------------------------------------------

1
int printf(const char *format, ...);        

  --------------------------------------------------------------------------------

  注意到,在函數(shù)中聲明其參數(shù)是可變的方法是三個(gè)點(diǎn)“...”,但同時(shí),這個(gè)函數(shù)必須要有一個(gè)固定的參數(shù),比如printf里面的這個(gè)format,也就是說變參函數(shù)的參數(shù)數(shù)目至少是一個(gè)。這是由C語言中實(shí)現(xiàn)變參的原理---計(jì)算堆棧地址---決定的。順著printf函數(shù)我們來看看它的定義是什么:

  --------------------------------------------------------------------------------

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int __printf(const char *format, ...)        
 
{        
 
va_list arg;        
 
int done;        
 
va_start(arg, format);        
 
done = vfprintf(stdout, format, arg);        
 
va_end(arg);        
 
return done;        
 
}        

  --------------------------------------------------------------------------------

  (注意到庫函數(shù)中內(nèi)部定義的變量和函數(shù)用了雙下劃線開頭,這也是我們寫應(yīng)用程序時(shí)盡量不要用雙下劃線開頭的原因,我們也不應(yīng)該使用單下劃線開頭的函數(shù)和變量,因?yàn)槟且彩窍到y(tǒng)保留的)

  其中發(fā)現(xiàn)__printf函數(shù)里用了va_list,va_start,va_end等宏,事實(shí)上,在__printf中調(diào)用的vfpirntf函數(shù)還用到了一個(gè)叫做va_arg的宏,這幾個(gè)宏就是編寫變參函數(shù)的關(guān)鍵。現(xiàn)在我們自己寫一個(gè)最簡單的變參函數(shù),先來個(gè)感性認(rèn)識:

  --------------------------------------------------------------------------------

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include        
 
#include        
 
void simple_va_fun(int i, ...)        
 
{        
 
va_list arg_ptr; //定義一個(gè)用來指向函數(shù)變參列表的指針arg_ptr        
 
int j;        
 
va_start(arg_ptr, i); //使arg_ptr指向第一個(gè)可變參數(shù)        
 
j = va_arg(arg_ptr, int); //取得arg_ptr當(dāng)前所指向的參數(shù)的值,并使arg_ptr指向下一個(gè)參數(shù)        
 
va_end(arg_ptr); //指示提取參數(shù)結(jié)束        
 
printf("%d %d ", i, j);        
 
return;        
 
}        
 
int main(void)        
 
{        
 
simple_va_fun(3, 4);        
 
return 0;        
 
}        

  --------------------------------------------------------------------------------

  如代碼中的注釋所示,arg_ptr實(shí)際上是一個(gè)指向函數(shù)變參列表的指針,va_list實(shí)際上是void指針類型。

  va_start用來初始化這個(gè)指針,使之指向變參列表中的第一個(gè)參數(shù),注意到它的第二個(gè)參數(shù)是變參函數(shù)的那個(gè)固定參數(shù)。

  va_arg利用已經(jīng)初始化了的arg_ptr指針來取得變參列表中各個(gè)參數(shù)的值,第一個(gè)參數(shù)是變參列表指針,第二個(gè)參數(shù)是當(dāng)前參數(shù)的類型。

  va_end宏用來提示結(jié)束參數(shù)結(jié)束,在LINUX的glibc實(shí)現(xiàn)中,va_end實(shí)際上就是一個(gè)空語句(void)0

  各個(gè)宏定義在頭文件stdarg.h中聲明,因此我們需要包含這個(gè)頭文件。其具體的定義如下:

  --------------------------------------------------------------------------------

1
2
3
4
5
6
7
8
9
10
11
#define _AUPBND (sizeof(acpi_native_int) - 1)        
 
#define _ADNBND (sizeof(acpi_native_int) - 1)        
 
#define _bnd(X, bnd) (((sizeof(X)) + (bnd)) & (~(bnd)))        
 
#define va_start(ap, A) (void)((ap) = (((char *)&(A)) + (_bnd(A, _AUPBND)))        
 
#defind va_arg(ap, T) (*(T*)(((ap) += (_bnd(T, _AUPBND))) - (_bnd(T, _ADNBDN))))        
 
#define va_end(ap) (void)0        

  --------------------------------------------------------------------------------

  這些宏定義都比較繁瑣,主要目的是為了適應(yīng)不同系統(tǒng)的地址對齊問題。

  上面說過,va_start的功能實(shí)際上是使ap指針指向第一個(gè)變參,A就是我們的第一個(gè)固定參數(shù),不考慮地址對齊,最簡單的辦法當(dāng)然如下:

  ap = &A + sizeof(A)

  上述代碼其實(shí)也是實(shí)現(xiàn)的這個(gè)簡單的功能,但經(jīng)過宏_AUPBND和_bnd之后,就能保證ap指向的地址至少是關(guān)于acpi_native_int對齊的,打個(gè)比方,如果此時(shí)A的地址是0x0003,而且A的類型占用4個(gè)字節(jié),而當(dāng)前系統(tǒng)要求4個(gè)字節(jié)對齊,那么就讓_AUPBND中的sizeof參數(shù)為4,經(jīng)過多次宏替代之后ap的地址值就會(huì)是0x0008,而簡單地用上面的算式ap = &A + sizeof(A)計(jì)算出的結(jié)果是0x0007。

  同樣地,va_arg宏替代在不考慮任何移植性問題時(shí),要取得當(dāng)前變參的值并使指針指向下一個(gè)參數(shù)最簡單的辦法如下:

  *((ap+=sizeof(T)) - sizeof(T))

  這個(gè)需要稍微解釋一下,首先,C里面的參數(shù)壓棧是從右到左順序壓棧的,因此可以想象,第一個(gè)固定參數(shù)在棧頂(LINUX進(jìn)程映像中棧是倒著增長的,這個(gè)地址是所有參數(shù)中最小的),第二個(gè)參數(shù)(也就是第一個(gè)變參)在緊接著固定參數(shù)之上,以此類推。因此,要想ap指針不斷指向下一個(gè)參數(shù),就必須讓它每次都加上當(dāng)前指向的變量所占內(nèi)存的大小即 ap+=sizeof(T) 的含義。

  接下來,利用這個(gè)地址值又減去sizeof(T),實(shí)際上地址值又回到上一個(gè)參數(shù)處(注意,此時(shí)ap指針的值并未改變,也就是說,va_arg宏實(shí)現(xiàn)獲取第一個(gè)變參的值的時(shí)候是先使ap指向第二個(gè)變參,然后再去獲取第一個(gè)變參的值),然后取值。

  va_end宏就比較簡單了,雖然各種平臺的實(shí)現(xiàn)細(xì)節(jié)不一樣,但是道理都是一樣的,在glibc中va_end被簡單地實(shí)現(xiàn)為一個(gè)空語句。

  由此可見,實(shí)際上C語言的所謂變參函數(shù)是很笨的,它基本上啥智能都沒有,不能跟C++的多態(tài)性和符號重載相比,我們在傳遞參數(shù)的時(shí)候雖然可以傳遞不定個(gè)數(shù)的參數(shù),但是這些參數(shù)都必須在函數(shù)實(shí)現(xiàn)中給予一一處理。所以我還是比較推崇C++呵呵!

  至于printf這個(gè)調(diào)皮鬼,上面看到它的原型了,里面還調(diào)用了vfprintf函數(shù),這個(gè)函數(shù)就不分析了(實(shí)在太長了),它里面就用了va_arg來獲取各個(gè)變參的值。printf之所以可以識別各種變量類型,是因?yàn)槟阏{(diào)用它的時(shí)候必須用printf修飾符,也就是%d,%f,%s等等來指定你的參數(shù),printf是很笨的,它是不知道的。

【C語言中變參函數(shù)的實(shí)現(xiàn)細(xì)節(jié)】相關(guān)文章:

C語言中返回字符串函數(shù)的實(shí)現(xiàn)方法09-19

C語言中函數(shù)的區(qū)分08-30

c語言中time函數(shù)的用法08-27

C語言中g(shù)ets()函數(shù)知識08-10

C語言中strpbr()函數(shù)的用法07-25

C語言中isalnum()函數(shù)和isalpha()函數(shù)的對比10-12

C語言中函數(shù)的區(qū)分有哪些10-25

在C語言中函數(shù)調(diào)用方式的區(qū)別09-01

C語言中strstr()函數(shù)的使用分析08-03

C語言中實(shí)現(xiàn)KMP算法實(shí)例08-09