c語言中的voidprintlogcharformat這是什

2021-03-22 13:45:43 字數 4703 閱讀 3065

1樓:匿名使用者

表示printlog這個函式,接受任意多個引數,第一個引數必須為char *型別,後面有多少個引數都可以,沒有也可以。

c語言對可變引數的支援通過stdarg.h來實現,原理很簡單。

首先讓編譯器識別...,允許函式呼叫傳入任意數量的引數而不產生警告。然後在函式體裡通過stdarg.h提供的幾個巨集來獲取引數。

這是stdarg.h提供的用於實現變長引數的幾個巨集:

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

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

va_start用來初始化,就是將指標指向棧中變長引數的起始位置。va_arg用來取引數,即將指標轉換為指定型別的指標,取值。再將指標進行偏移。

va_end用來完成清理工作,也就是把指標置為null。

例子:int sum(unsigned int n,...)

va_end(args);

return sum;

}這段**實現了對任意數量的整數的求和,整數的數目由n指定。所以說,變長引數的函式一定有一個已知型別的引數,即第一個引數,用來告知函式,一共傳入了多少個引數,每個引數該取多長。

2樓:匿名使用者

是可變引數,是c的一個語法現象,我在電腦上儲存的一些資料,希望對你有用。

一、什麼是可變引數

我們在c語言程式設計中有時會遇到一些引數個數可變的函式,例如printf()函式,其函式原型為:

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

它除了有一個引數format固定以外,後面跟的引數的個數和型別是可變的(用三個點"…"做引數佔位符),實際呼叫時可以有以下的形式:

printf("%d",i);

printf("%s",s);

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

以上這些東西已為大家所熟悉。但是究竟如何寫可變引數的c函式以及這些可變引數的函式編譯器是如何實現,這個問題卻一直困擾了我好久。本文就這個問題進行一些**,希望能對大家有些幫助.

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

我們知道va_start,va_arg,va_end是在stdarg.h中被定義成巨集的, 由於1)硬體平臺的不同 2)編譯器的不同,所以定義的巨集也有所不同,下面看一下vc++6.0中stdarg.

h裡的**(檔案的路徑為vc安裝目錄下的\vc98\include\stdarg.h)

typedef char * va_list;

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

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

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

下面我們解釋這些**的含義:

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

2、定義_intsizeof(n)主要是為了某些需要記憶體的對齊的系統.這個巨集的目的是為了得到最後一個固定引數的實際記憶體大小。在我的機器上直接用sizeof運算子來代替,對程式的執行結構也沒有影響。

(後文將看到我自己的實現)。

3、va_start的定義為 &v+_intsizeof(v) ,這裡&v是最後一個固定引數的起始地址,再加上其實際佔用大小後,就得到了第一個可變引數的起始記憶體地址。所以我們執行va_start(ap, v)以後,ap指向第一個可變引數在的記憶體地址,有了這個地址,以後的事情就簡單了。

這裡要知道兩個事情:

⑴在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)) )

這個巨集做了兩個事情,

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

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

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

在這裡大家要注意一個問題:由於引數的地址用於va_start巨集,所以引數不能宣告為暫存器變數或作為函式或陣列型別. 關於va_start, va_arg, va_end的描述就是這些了,我們要注意的 是不同的作業系統和硬體平臺的定義有些不同,但原理卻是相似的.

三、可變引數在程式設計中要注意的問題

因為va_start, va_arg, va_end等定義成巨集,所以它顯得很愚蠢, 可變引數的型別和個數完全在該函式中由程式**控制,它並不能智慧 地識別不同引數的個數和型別. 有人會問:那麼printf中不是實現了智慧識別引數嗎?

那是因為函式 printf是從固定引數format字串來分析出引數的型別,再呼叫va_arg 的來獲取可變引數的.也就是說,你想實現智慧識別可變引數的話是要通過在自己的程式裡作判斷來實現的. 例如,在c的經典教材《the c programming language》的7.

3節中就給出了一個printf的可能實現方式,由於篇幅原因這裡不再敘述。

四、小結:

1、標準c庫的中的三個巨集的作用只是用來確定可變引數列表中每個引數的記憶體地址,編譯器是不知道引數的實際數目的。

2、在實際應用的**中,程式設計師必須自己考慮確定引數數目的辦法,如

⑴在固定引數中設標誌-- printf函式就是用這個辦法。後面也有例子。

⑵在預先設定一個特殊的結束標記,就是說多輸入一個可變引數,呼叫時要將最後一個可變引數的值設定成這個特殊的值,在函式體中根據這個值判斷是否達到引數的結尾。本文前面的**就是採用這個辦法.

無論採用哪種辦法,程式設計師都應該在文件中告訴呼叫者自己的約定。

3、實現可變引數的要點就是想辦法取得每個引數的地址,取得地址的辦法由以下幾個因素決定:

①函式棧的生長方向

②引數的入棧順序

③cpu的對齊方式

④記憶體地址的表達方式

結合源**,我們可以看出va_list的實現是由④決定的,_intsizeof(n)的引入則是由③決定的,他和①②又一起決定了va_start的實現,最後va_end的存在則是良好程式設計風格的體現,將不再使用的指標設為null,這樣可以防止以後的誤操作。

4、取得地址後,再結合引數的型別,程式設計師就可以正確的處理引數了。理解了以上要點,相信稍有經驗的讀者就可以寫出適合於自己機器的實現來。

C語言中的問題,c語言中 p , p 的問題

講一下vc6.0的流程 1.a 0 前置自減運算子先運算,結果使a 02.a a 0 後置運算子在表示式中先使用,後運算,所以a 的值還是0 3.0 a 0 後置運算子在表示式中先使用,後運算,所以a 的值還是0 4.0 a 0 1 1 前置運算子先運算,結果使a變為1 所以結果sum 1 運算後,...

c語言中的conio h方面的,C語言中 conio h 是幹什麼用的,能具體解釋一下嗎

標頭檔案 conion.h 是用來清屏的 在後面的 中巢狀 clrscr 來完成 清屏 清行 向後刪行 插行的操作真有些cced wps等編輯軟體的風格,就是在你平時的螢幕設計中也用少不了。返回座標的函式則給你的設計提供每一個環節的游標座標值。有利於你前後響應。字 符亮度的改變則會讓你的字元顯示出層...

c語言中關於,C語言中關於ch ch a A 的大小寫轉換,為什麼不直接是「ch ch 32」?請大俠詳解!!!

兩種寫法結果一致,之所以寫成第一個 是 可閱讀性 更好,通過 很容易明白在做什麼,而第二個ch 32,32 來的?為什麼是32?非編寫者閱讀程式會很痛苦。因為比較清楚,讓閱讀 的人一看就知道這行 的目的是執行大小寫轉化 以後寫程式多向此學習,類似情況要多采用全字元處理,這樣可以避免資料型別轉換出現問...