C中的可變引數研究

2021-04-12 13:04:38 字數 2802 閱讀 6116

一. 何謂可變引數

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

這是使用過c語言的人所再熟悉不過的printf函式原型,它的引數中就有固定引數format和可變引數(用」…」表示). 而我們又可以用各種方式來呼叫printf,如:

printf("%d",value);

printf("%s",str);

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

二.實現原理

c語言用巨集來處理這些可變引數。這些巨集看起來很複雜,其實原理挺簡單,就是根據引數入棧的特點從最靠近第乙個可變引數的固定引數開始,依次獲取每個可變引數的位址。下面我們來分析這些巨集。在vc中的stdarg.h標頭檔案中,針對不同平台有不同的巨集定義,我們選取x86平台下的巨集定義:

typedef char *va_list;

/*把va_list被定義成char*,這是因為在我們目前所用的pc機上,字元指標型別可以用來儲存記憶體單元位址。而在有的機器上va_list是被定義成void*的*/

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

/*_intsizeof(n)巨集是為了考慮那些記憶體位址需要對齊的系統,從巨集的名字來應該是跟sizeof(int)對齊。一般的sizeof(int)=4,也就是引數在記憶體中的位址都為4的倍數。比如,如果sizeof(n)在1-4之間,那麼_intsizeof(n)=4;如果sizeof(n)在5-8之間,那麼_intsizeof(n)=8。*/

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

/*va_start的定義為 &v+_intsizeof(v) ,這裡&v是最後乙個固定引數的起始位址,再加上其實際占用大小後,就得到了第乙個可變引數的起始記憶體位址。所以我們執行va_start(ap, v)以後,ap指向第乙個可變引數在的記憶體位址*/

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

/*這個巨集做了兩個事情,

①用使用者輸入的型別名對引數位址進行強制型別轉換,得到使用者所需要的值

②計算出本引數的實際大小,將指標調到本引數的結尾,也就是下乙個引數的首位址,以便後續處理。*/

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

/*x86平台定義為ap=(char*)0;使ap不再 指向堆疊,而是跟null一樣.有些直接定義為((void*)0),這樣編譯器不會為va_end產生**,例如gcc在linux的x86平台就是這樣定義的. 在這裡大家要注意乙個問題:由於引數的位址用於va_start巨集,所以引數不能宣告為暫存器變數或作為函式或陣列型別. */

以下再用圖來表示:

在vc等絕大多數c編譯器中,預設情況下,引數進棧的順序是由右向左的,因此,引數進棧以後的記憶體模型如下圖所示:最後乙個固定引數的位址位於第乙個可變引數之下,並且是連續儲存的。

|——————————————————————————|

|最後乙個可變引數 | ->高記憶體位址處

|——————————————————————————|

...................

|——————————————————————————|

|第n個可變引數 | ->va_arg(arg_ptr,int)後arg_ptr所指的地方,

| | 即第n個可變引數的位址。

|——————————————— |

………………………….

|——————————————————————————|

|第乙個可變引數 | ->va_start(arg_ptr,start)後arg_ptr所指的地方

| | 即第乙個可變引數的位址

|——————————————— |

|———————————————————————— ——|

| ||最後乙個固定引數 | -> start的起始位址

|—————————————— —| .................

|—————————————————————————— |

| ||——————————————— |-> 低記憶體位址處

三.printf研究

下面是乙個簡單的printf函式的實現,參考了中的156頁的例子,讀者可以結合書上的**與本文參照。

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

在intel+win2k+vc6的機器執行結果如下:

the first test:i=1234

the secend test:i=1234; 0xabcd;j=5678;

四.應用

求最大值:

#include //不定數目引數需要的巨集

int max(int n,int num,...)

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

return m;

}main()

c可變引數研究

們知道va start,va arg,va end是在stdarg.h中被定義成巨集的,由於1 硬體平台的不同 2 編譯器的不同,所以定義的巨集也有所不同,下 面以vc 中stdarg.h裡x86平台的巨集定義摘錄如下 號表示折行 typedef char va list define intsiz...

C語言可變引數研究

一 何謂可變引數 int printf const char format,這是使用過c語言的人所再熟悉不過的printf函式原型,它的引數中就有固定引數format和可變引數 用 表示 而我們又可以用各種方式來呼叫printf,如 printf d value printf s str print...

可變引數研究

可變引數的研究,可變引數底層是陣列,確定這個結論之後要實際驗證測試 package day9yue1 public class test1 如果這2個方法都叫test,編譯錯誤,說明引數是一模一樣,沒有過載 結論 string s 和 string.s 結果是一樣的 public void test...