C語言中可變參數的原理——printf()函數


函數原型: int printf(const char *format[,argument]...)

返 回 值: 成功則返回實際輸出的字符數,失敗返回-1.

函數說明:

使用過C語言的人所再熟悉不過的printf函數原型,它的參數中就有固定參數format和可變參數(用"…"表示),format后面的參數個數不確定,且類型也不確定,這些參數都存放在棧內。而程序員又可以用各種方式來調用printf,如: 

            printf("%d ",value);   

            printf("%s ",str);   

            printf("the number is %d,string is:%s ",value,str); 

調用printf()函數時,根據format里的格式("%d %f...")依次將棧里參數取出。而取出動作要用到va_arg、va_end、va_start這三個宏定義,再加上va_list。

     (1)va_list事實上是一char *類型,即:

            typedef char* va_list;

     (2)三個宏定義:

        #define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) 

為了字節對齊,將n的長度化為int長度的整數倍。

補充:對((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) 的解釋

1.舉個栗子解釋一下內存對齊是什么?

      比方說有一個箱子可以裝4個瓶子,我有8個瓶子 ,那么我需要2個箱子如果我有10個瓶

子呢,我不能說我需要10除4,需要2.5個箱子吧。實際上我需要3個箱子,那怎么求我實際需

要的箱子數呢?

用一個容易理解的公式來求上述問題:

設我的瓶子數為B,我需要的箱子數為C,一個箱子最多可以裝A個瓶子。

公式:C=(B+A-1)/A

帶入幾個例子:

B=10,  A=4   得C=13/4 ,C=3        每個箱子最多能裝4個瓶子,10個瓶子需要3個箱子

B=14,A=4  得C=17/4  ,C=4        每個箱子最多能裝4個瓶子,14個瓶子需要4個箱子

2.細致的分析一下((sizeof(n)+sizeof(int)-1)&~(sizeof(int)-1)) 

((sizeof(n)+sizeof(int)-1)&~(sizeof(int)-1)) :  ((sizeof(n)+sizeof(int)-1)其實就是 (B+A-1)

a、(sizeof(n)是我們需要的實際內存大小,相對於我的瓶子數。

b、sizeof(int)是內存大小分配的最小刻度,相對於箱子最多可以裝瓶子的個數。

c、在32位系統中,~(sizeof(int)–1) )展開為~(4-1)=~(00000011b)=11111100b,這樣

任何數 & ~(sizeof(int)–1) )后最后兩位肯定為0,也就是4的整數倍。

// e、((sizeof(n)+sizeof(int)-1)&~(sizeof(int)-1)) & ~(sizeof(int)-1))其實就是  除以A或者說除

// 以4;((sizeof(n)+sizeof(int)-1)&~(sizeof(int)-1)) 其實就是實現栗子中的(B+A-1)/A。

// (此處描述錯誤,並不是/4)

總結:

_INTSIZEOF(n)整個做的事情就是將n的長度化為int長度的整數倍。

比如n為5,二進制就是101b,int長度為4,二進制為100b,那么n化為int長度的整數倍就應

該為8。~(sizeof(int) – 1) )就應該為~(4 - 1) = ~(00000011b) = 11111100b,這樣任何

數 & ~(sizeof(int) – 1) )后最后兩位肯定為0,就肯定是4的整數倍了。

(sizeof(n) + sizeof(int) – 1)就是將大於4m但小於等於4(m + 1)的數提高到大於等於

4(m + 1)但小於4(m + 2),這樣再& ~(sizeof(int) – 1))后就正好將原長度補齊到4的倍數

了。

        #define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) ) 

通過該宏定義可以獲取到可變參數表的首地址,並將該地址賦給指針ap。

           

#define va_arg(ap,type) ( *(type *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) 

通過該宏定義可以獲取當前ap所指向的可變參數,並將指針ap指向下一個可變參

數。注意,該宏的第二個參數為類型。

        #define va_end(ap)          ( ap = (va_list)0) 

通過該宏定義可以結束可變參數的獲取。

可以看出,該函數的參數格式不固定,參數類型不固定。在C語言中使用宏來處理這些可變參數。這些宏看起來很復雜,其實原理挺簡單,即根據參數入棧的特點從最靠近第一個可變參數的固定參數開始,依次獲取每個可變參數的地址。

   

程序員通過這三個宏定義就可以實現對可變參數的處理。例如:

1 #include <stdio.h>  
2   
3 typedef char* va_list;   
4
5 #define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))   
6 #define va_start(ap,v)  ( ap = (va_list)&v + _INTSIZEOF(v) )   
7 #define va_arg(ap,type) ( *(type *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )   
8 #define va_end(ap)      ( ap = (va_list)0 )   
9   
10 int cal_val(int c, ...)   
11 {   
12         int sum = c;   
13         va_list ap;              //聲明指向char型的指針  
14     va_start(ap, c);         //獲取可變參數列表的首地址,並賦給指針ap  
15   
16     c = va_arg(ap, int);     //從可變參數列表中獲取到第一個參數(返回值即為參數)  
17     while(0 != c)   
18     {   
19         sum += c;   
20         c = va_arg(ap,int);  //循環的從可變參數列表中獲取到參數(返回值即為參數)  
21     }  
22     va_end(ap);              //結束從可變參數列表中獲取參數  
23     return sum;   
24 }    
25    
26 int main(int argc, char* argv[])   
27 {   
28         int value1, value2; 
29         value1 = cal_val(1,2,3,4,5,6,7,8,9,0);   
30         printf("value1=%d/n",value1);  
31         value2 = cal_val(6,7,8,9,0);   
32


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM