在呼叫支援可變引數格式化的函式時的乙個很掩蔽問題

2021-06-22 13:12:12 字數 1653 閱讀 4502

很多時候我們都希望一些函式能夠支援可變引數,這樣的函式或是用來進行字串的格式化,或是呼叫函式時函式本身支援引數的格式化(這樣在呼叫函式之前不用另外格式化,呼叫的函式本身就支援引數的格式化)。比如c語言中的printf函式,mfc中的cstring的format函式。它們內部均是使用va_start、va_list、va_end實現引數的解析的。下面給出duilib中的cstdstring::format 的函式實現,簡單的檢視一下是如何解析的。

int cstdstring::format( lpctstr pstrformat, ... )

; va_list arglist;

va_start( arglist, pstrformat );

// wvsprintf不支援浮點格式,所以換成_vstprintf

int iret = ::_vstprintf( szbuffer, sformat, arglist );

va_end( arglist );

assign( szbuffer );

return iret;

}

上面簡要的說明了使用va_start、va_list、va_end實現了對可變引數格式化的支援,那其中的陷阱在什麼地方呢?在日常的**開發過程中,為了定位問題我們需要新增相關的列印資訊,或列印到控制台視窗上,或是將日誌寫入到檔案中。我們就以c語言中列印輸出函式printf為例吧。該函式支援直接將字串列印出來,也支援對可變引數的格式化輸出,問題就掩藏在直接列印字串這個功能上。大家看下面的**會不會出問題:

char achbuf = ; // 包含格式化符%d

printf(achbuf);

上面的**可以測試一下是沒問題,achbuf中包含了乙個%d的格式化,但是呼叫achbuf時,並沒有傳待格式化的引數,執行可能會出問題,但實際上執行時是不會報錯的。那我們將字串中的%d改為%s後,再測試一下,結果程式發生崩潰了。那為什麼%d沒問題,而%s就出現了崩潰呢?可能是在引數列表中可以隨便找個整數值,但字串就不太好找了。因為沒有傳待格式化的字串引數,所以產生了崩潰。

上述**只是一些測試**,是我們人為的在待輸出的字串中加入了格式化符,在實際**執行的過程中,一般不會出現這樣的字串中包含有格式化的情況。那在哪些個別情況下會出現呢?比如在聊天對話方塊中,使用者可以隨意的輸入,也可能聊天的雙方也在討論程式設計的問題,就有可能出現輸入包括%s的字串了。我們可能為了定位問題,直接將使用者輸入的內容列印出來,呼叫支援可變引數格式化的函式將字串直接列印出來,此時就可能出現崩潰。因為出現問題的場景比較少,所以這個問題非常隱蔽,很難被發現。這個問題在我們實際的**開發過程中發生過兩次,一次是重構**發現的,一次是排查崩潰**發現的。

這個問題對於幾乎所有支援可變引數格式化的函式,都是存在的,不管是系統函式還是使用者自定義函式。那這個問題該如何解決呢?可以採用規避的辦法,不直接列印字串,而是將字串作為待格式化的引數傳進去,即如下所示:

char achbuf = ; // 包含格式化符%s

printf("%s", achbuf);

這樣就沒有問題了,可以測試一下。

所以在直接列印使用者輸入字串的場合,就要特別留意這個問題了。

C語言可變引數和格式化輸出

我們知道在c 中可以通過函式過載的方式為函式提供接受可變個引數的功能,而在c語言中並沒有過載的機制,不過c語言仍然提供了在標頭檔案 stdarg.h 中提供了類似的功能。在標頭檔案中提供了va list型別來存放可變引數列表 這個型別應該是乙個指標 以及三個巨集來實現相關的操作,三個巨集的原型如下 ...

php呼叫可變函式,PHP呼叫參數量可變的函式

所以我遇到了一些問題。我知道乙個解決方案,但它看起來不太乾淨,我想知道是否有更好的解決方案。我正在編寫乙個mysqli包裝器,用於執行準備好的語句。因為它是乙個包裝器,可以重用 動態 返回的列數取決於查詢,並且不是靜態的。我已經找到了乙個解決這個問題的方法,似乎每個人都在使用 call user f...

動態的呼叫可變引數函式

最近,碰到了乙個奇怪的問題 如何在函式中動態的呼叫可變引數函式。例如說,有某個可變引數函式 void func1 int a,現在給出乙個個數不定的動態陣列,把裡面的數值按順序的作為可變引數傳遞進 func1 函式中。當然,如果允許改變 func1 的定義,那麼我相信每個人都可以輕鬆的完成這個任務,...