2009-10-01 164 views
9

以下代碼會導致錯誤並殺死我的應用程序。這很有意義,因爲緩衝區只有10個字節長,文本長度爲22個字節(緩衝區溢出)。帶緩衝區太小的sprintf_s

char buffer[10];  
int length = sprintf_s(buffer, 10, "1234567890.1234567890."); 

如何捕獲此錯誤,以便報告而不是崩潰我的應用程序?

編輯:

閱讀下面的評論我_snprintf_s去後。如果它返回-1值,那麼緩衝區沒有更新。

length = _snprintf_s(buffer, 10, 9, "123456789"); 
printf("1) Length=%d\n", length); // Length == 9 

length = _snprintf_s(buffer, 10, 9, "1234567890.1234567890."); 
printf("2) Length=%d\n", length); // Length == -1 

length = _snprintf_s(buffer, 10, 10, "1234567890.1234567890."); 
printf("3) Length=%d\n", length); // Crash, it needs room for the NULL char 
+0

傳遞的緩衝區大小和緩衝區大小減去一個是鈍的,而且容易出錯。您應該更喜歡下面描述的變體: length = _snprintf_s(buffer,_TRUNCATE,「1234567890.1234567890。」); 由於省略了第一個大小參數,因此編譯器使用模板過載來推斷大小。 _TRUNCATE是一個特殊的值,它可以完成它所說的事情。沒有幻數,現在你的代碼是安全的,可維護的,並且是一個很好的例子。 如果你喜歡這個評論和_snprintf_s,那麼你應該選擇我的答案,而不是危險的snprintf/_snprintf答案。 – 2014-12-17 22:40:06

回答

5

而不是sprintf_s,你可以使用snprintf(在Windows上另一個_snprintf)。

#ifdef WIN32 
#define snprintf _snprintf 
#endif 

char buffer[10];  
int length = snprintf(buffer, 10, "1234567890.1234567890."); 
// unix snprintf returns length output would actually require; 
// windows _snprintf returns actual output length if output fits, else negative 
if (length >= sizeof(buffer) || length<0) 
{ 
    /* error handling */ 
} 
+4

還有一個snprintf_s。 – Joe 2009-10-01 20:10:56

+2

注意:出於安全考慮,如果沒有足夠的空間,緩衝區的內容可能不會被空終止。 – Managu 2009-10-01 20:12:51

+2

@Managu:如果MS聲稱符合C99-它不會 - 斷言將是假的; C99標準要求snprintf()以null結束字符串,除非字符串的長度爲0. 7.19.6.5節:如果n爲零,則不會寫入任何內容......否則,n-1之外的輸出字符將被丟棄而不是而不是寫入數組,並且在實際寫入數組的字符末尾寫入空字符 。如果複製發生在重疊的對象 之間,則行爲是不確定的。 – 2009-10-01 22:24:50

0

從MSDN:

sprintf_s和sprintf之間的另一個主要區別是,sprintf_s帶長度參數指定在字符的輸出緩衝區的大小。如果緩衝區對於正在打印的文本太小,則將緩衝區設置爲空字符串,並調用無效參數處理程序。與snprintf不同,sprintf_s保證緩衝區將以空終止(除非緩衝區大小爲零)。

所以你寫的應該是正確的。

+4

默認的「無效參數處理程序」終止進程。 – 2009-10-01 19:49:47

+0

是真的,但是安裝一個不容易,如果緩衝區太小,會導致sprintf_s返回-1 – stijn 2009-11-11 08:31:49

0

看起來像你寫的MSVC的某種?

我認爲sprintf_s的MSDN文檔說它聲明死亡,所以我不太確定你是否可以通過編程來捕獲它。

正如LBushkin建議的那樣,使用管理字符串的類更好。

16

這是設計。 sprintf_s的整個點以及*_s系列的其他功能都是爲了捕獲緩衝區溢出錯誤,並將它們視爲先決條件違規。這意味着它們並非真正意味着可以恢復。這樣做的目的只是爲了發現錯誤 - 如果您知道該字符串對於目標緩衝區來說可能太大,則不應該調用sprintf_s。在這種情況下,首先使用strlen檢查並決定是否需要修剪。

+0

我不同意。調用sprintf_s的目標緩衝區太小是完全合理的,只要您使用_TRUNCATE標誌來指示。好吧,從技術上來說_TRUNCATE需要使用snprintf_s而不是sprintf_s,但我的觀點大部分都是站得住腳的。 使用strlen往往不適用或不方便,但使用_TRUNCATE通常是微不足道的和適當的。 – 2015-11-16 23:05:16

+0

我認爲使用'snprintf_s'是至關重要的區別,並不僅僅是技術性。 – 2015-11-17 02:52:34

+0

這是一個關鍵的區別,當然。但我認爲你的回答看起來像功能家族不能截斷,這可能是誤導。 – 2015-11-18 17:05:20

0

請參閱TR24731的第6.6.1節,它是由Microsoft實施的ISO C Committee版本的功能。它提供了功能set_constraint_handler(),abort_constraint_handler()ignore_constraint_handler()的功能。

Pavel Minaev有評論認爲微軟的實施不符合TR24731提案(這是'Type 2 Tech Report'),所以你可能無法干預,或者你可能不得不做一些事情與TR指示應該完成的不同。爲此,仔細檢查MSDN。

+1

不幸的是,MSVC並沒有完全實現TR24731 - 特別是,它沒有專門實現你引用的函數(同樣,它們的名字也以'_s'結尾 - 即'set_constraint_handler_s')。 – 2009-10-01 20:04:07

+1

但根據http://msdn.microsoft.com/en-us/library/ksazx244%28VS.80%29.aspx有一個函數_set_invalid_parameter_handler()函數,可用於更改中止程序的默認行爲。 – 2009-10-02 02:26:41

5

這適用於VC++,比使用的snprintf(當然較安全_snprintf)更安全:

void TestString(const char* pEvil) 
{ 
    char buffer[100]; 
    _snprintf_s(buffer, _TRUNCATE, "Some data: %s\n", pEvil); 
} 

的_TRUNCATE標誌表示該字符串應該被截斷。在這種形式下,緩衝區的大小實際上並沒有被傳入,這(矛盾的!)是什麼讓它如此安全。編譯器使用模板魔術來推斷緩衝區大小,這意味着它不能被錯誤地指定(一個令人驚訝的常見錯誤)。這種技術可以應用到創建其他安全繩包裝,在我的博客文章描述如下: https://randomascii.wordpress.com/2013/04/03/stop-using-strncpy-already/

+0

查看_snprintf_s的MSDN文檔,看起來您已經在_snprintf_s調用中忘記了一個參數。這個參數應該出現在緩衝區和_TRUNCATE之間,被稱爲sizeOfBuffer – user1741137 2017-10-22 14:02:24

+1

我沒有忘記一個參數 - 代碼編譯完全安全。您需要重新閱讀文檔。我正在使用_snprintf_s模板覆蓋來告訴編譯器推斷緩衝區大小。 我已經看到了數百個程序員顯式傳遞了緩衝區大小的地方,並且傳遞了*錯誤*大小。只有讓編譯器推斷緩衝區大小,才能避免這種嚴重的錯誤。 我在我的解決方案中鏈接到的文章中提到了這種技術。強力推薦。 – 2017-10-23 16:07:40