C語言輔導 不定引數在C語言中的應用例項

2021-05-01 12:56:57 字數 3478 閱讀 1065

c語言輔導:不定引數在c語言中的應用例項

2023年下半年全國計算機等級考試你準備好了沒?考計算機等級二級考試的朋友,2023年下半年全國計算機等級考試時間是2023年9月19日至23日。

不定引數在c語言中的應用例項:不定引數當年做為c/c++語言乙個特長被很多人推崇,但是實際上這種技術並沒有應用很多。除了格式化輸出之外,我實在沒看到多少應用。主要原因是這種技術比較麻煩,***也比較多,而一般情況下過載函式也足以替換它。儘管如此,既然大家對它比較感興趣,我就簡單總結一下它的使用和需要注意的常見問題。 

剛學c語言的時候,一般人都會首先接觸printf函式。通過這個函式,你可以列印不定個數的變數到螢幕,如: 

printf("%d", 3);

printf("%d,%d",3,4); 

上述**看似簡單,實際上卻需要我們解決許多問題。在我們設計printf的時候,我們是不知道到底會傳入幾個引數的。在這種未知的情況下,我們需要解決下面幾個問題: 

怎麼告訴printf我們會傳入幾個引數 

printf怎麼去訪問這些引數 

函式呼叫完成後,系統怎麼把引數從傳遞用的堆疊中釋放 

為了解決這些問題,我們首先要解釋cdecl呼叫約定,所有使用不定引數的函式必須是使用cdecl(全域性函式)或者this call(類成員函式)呼叫約定。該約定對於引數傳遞規定如下: 

引數從右向左入棧(也就是如果你呼叫f(a,b,c),則c先入棧,然後是b,最後是a入棧) 

呼叫者負責清理堆疊 

其中第二點直接解決了前面三個問題中的第三個問題。我們來詳細說說其他兩個問題。

確定引數的個數 

在乙個函式中,一般有如下prelog**:

00401020 push ebp

00401021 mov ebp,esp

00401023 sub esp,48h

執行上述**之後,func(a,b,c)函式所處的堆疊上下文就變成如下布局:

其中,ebp指向儲存舊的ebp的堆疊記憶體的下乙個字的位址,ebp+8指向eip位址,ebp+12則指向函式呼叫的第乙個引數,而ebp和esp之間是用於臨時變數(也就是堆疊變數)的空間。 

注意,由於上述prelog**的存在,我們很容易通過ebp得到第乙個引數的位址,對於不定引數列表之前的型別固定的引數,我們也可以根據型別資訊得到其實際的位置(例如,第乙個引數的位置偏移第乙個引數的大小,就是第二個引數的位址)。 

注意不定引數函式有個限制,就是不定引數的列表必須在整個函式的引數列表的最後。我們不可以定義如下的函式: 

void func(int a, ……, int c)

所有型別固定的引數都必須出現在引數列表的開始。這樣根據前面的論述,我們就可以得到所有型別固定的引數。

在設計具有不定引數列表的函式的時候,我們有兩種方法來確定到底多少引數會被傳遞進來。 

方法1是在型別固定的引數中指明後面有多少個引數以及他們的型別。printf就是採用的這種方法,它的format引數指明後面每個引數的型別。 

方法2是指定乙個結束引數。這種情況一般是不定引數擁有同樣的型別,我們可以指定乙個特定的值來表示引數列表結束。下面這個sum函式就是乙個例子:

int sumi(int c, ...)

return sum;

} 使用這個函式的**為:

int main(int argc, char* argv)

訪問各個引數

其實前文已經告訴我們怎麼去訪問不定引數。va_start和va_arg函式可以被結合起來用於依次訪問每個函式,他們實際上都是巨集函式。 

在vc6,va_start函式定義為: #define _intsizeof(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )

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

其中_intsizeof(n)計算比n大的sizeof(int)的最小倍數,如果n=101,則_intsizeof(n)為104.

va_start執行完畢後,ap指向變數v後第乙個4位元組對齊的位址。例如,v的位址為0x123456, v的大小為13,則v後面的下乙個與字邊界對齊的位址為0x123456+0x0d=0x123463再調整為與4位元組對齊的下乙個位址,也就是0x123464.

va_arg函式定義為:

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

分析與va_start一樣,它的結果是使ap指向當前變數的下乙個變數。

這樣,我們只要在開始時使用va_start把不定引數列表賦值給ap,然後依次用va_arg獲得不同引數即可。

潛在問題

使用不定引數列表,有兩個問題特別需要注意。

問題1的理解相對簡單:我們在過載乙個函式的時候,不能依賴不定引數列表部分對函式進行區分。

假定我們定義兩個過載函式如下:

int func(int a, int b, ……)

int func(int a, int b, float c);

則上述函式會導致編譯器不知道怎麼去解釋func(1,2, 3.3),因為當第三個引數為浮點數時,兩個實現都可以滿足匹配要求。一般情況,個人建議對於不定引數函式不要去做過載。 

另外乙個問題是關於型別問題。絕大多數情況下,c和c++的變數都是強型別的,而不定引數列表屬於乙個特例。 

當我們呼叫va_arg的時候,我們指明下乙個引數的型別,而在執行的時候,va_arg正是根據這個資訊在堆疊上來找到對應的引數的。如果我們需要的型別和真實傳遞進來的引數完全一致時自然沒有問題,但是假如型別不一樣,則會有**煩。 

假如上面的的sumi函式,我們用下面方法呼叫:

int sum = sumi(1, 2.2, 3, 0) 

注意第二個引數我們傳入了乙個double型別的2.2,我們希望sumi在做加法時可以做隱式型別轉換,轉換為int進行計算。但是實際情況時,當我們分析到這個引數時,呼叫的是: 

c=va_arg(ap,int)

據前文va_arg的定義,這個巨集被翻譯成:

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

如果後面的+=計算出正確的位址,最後就變成

*(int*)addr

如果希望能得到正確的整數值,必須要求addr所在的位址是乙個真實的int型別。但是當我們傳入double時,實際上其記憶體布局和int完全不同,因此我們得不到需要的整數。感興趣的朋友可以用下面簡單的**做測試:

double a;

a=1.1;

int b = *(int*) & a; 

因此,當我們呼叫有不定引數列表的函式時,不要期望系統做隱式型別轉換,系統不會做這種檢查或者轉換,你給的引數型別必須嚴格和你希望的值一樣。

C語言輔導 不定引數在C語言中的應用例項

2009年下半年全國計算機等級考試你準備好了沒?考計算機等級二級考試的朋友,2009年下半年全國計算機等級考試時間是2009年9月19日至23日。不定引數在c語言中的應用例項 不定引數當年做為c c 語言乙個特長被很多人推崇,但是實際上這種技術並沒有應用很多。除了格式化輸出之外,我實 在沒看到多少應...

c語言中的不定引數

在採用c語言程式設計時,函式中的形式引數數目通常是確定的,在呼叫的時候要依次給出與形式引數對應的所有實際引數,但在某些情況下希望函式的引數個數可以根據需要確定,如printf,scanf函式等,c編譯器提供了一系列處理這種情況的巨集,以遮蔽不同的硬體平台造成的差異,增加程式的可移植性,這些巨集包括v...

C語言中的不定引數

1,最近剛剛知道c語言還有不定引數這麼個東東。2,解決方法 三個巨集的使用va arg va start 和va end 上述的巨集原型如下所示 type va arg va list argptr,type void va end va list argptr void va start va l...