[英]How to deal with bad_alloc in C++?
有一個名為foo
的方法有時會返回以下錯誤:
terminate called after throwing an instance of 'std::bad_alloc'
what(): std::bad_alloc
Abort
有沒有辦法可以使用try
- catch
塊來阻止此錯誤終止我的程序(我只想返回-1
)?
如果是這樣,它的語法是什么?
我還能如何處理 C++ 中的bad_alloc
?
通常,您不能也不應該嘗試響應此錯誤。 bad_alloc
表示由於沒有足夠的可用內存而無法分配資源。 在大多數情況下,您的程序無法應對這種情況,並且盡快終止是唯一有意義的行為。
更糟糕的是,現代操作系統經常過度分配:在這樣的系統上,即使沒有足夠的可用內存, malloc
和new
也可以返回一個有效的指針std::bad_alloc
永遠不會被拋出,或者至少不是一個可靠的標志內存耗盡。 相反,嘗試訪問分配的內存將導致無法捕獲的段錯誤(您可以處理段錯誤信號,但之后無法恢復程序)。
捕獲std::bad_alloc
時你唯一能做的就是記錄錯誤,並嘗試通過釋放未完成的資源來確保安全的程序終止(但這是在錯誤被拋出后的正常堆棧展開過程中自動完成的,如果該程序適當地使用了 RAII)。
在某些情況下,程序可能會嘗試釋放一些內存並重試,或者使用輔助內存(= 磁盤)而不是 RAM,但這些機會僅存在於具有嚴格條件的非常特定的場景中:
應用程序控制第 1 點的情況極為罕見——用戶空間應用程序永遠不會這樣做,這是一個系統范圍的設置,需要 root 權限才能更改。 1
好的,讓我們假設您已經確定了第 1 點。例如,您現在可以做的是對某些數據使用LRU 緩存(可能是一些可以按需重新生成或重新加載的特別大的業務對象)。 接下來,您需要將可能失敗的實際邏輯放入支持重試的函數中——換句話說,如果它被中止,您可以重新啟動它:
lru_cache<widget> widget_cache;
double perform_operation(int widget_id) {
std::optional<widget> maybe_widget = widget_cache.find_by_id(widget_id);
if (not maybe_widget) {
maybe_widget = widget_cache.store(widget_id, load_widget_from_disk(widget_id));
}
return maybe_widget->frobnicate();
}
…
for (int num_attempts = 0; num_attempts < MAX_NUM_ATTEMPTS; ++num_attempts) {
try {
return perform_operation(widget_id);
} catch (std::bad_alloc const&) {
if (widget_cache.empty()) throw; // memory error elsewhere.
widget_cache.remove_oldest();
}
}
// Handle too many failed attempts here.
但即使在這里,使用std::set_new_handler
而不是處理std::bad_alloc
提供相同的好處,而且會簡單得多。
1如果你創建不控制點1的應用程序,你讀這個答案,請拍我的電子郵件,我真的很好奇你的情況。
new
的 C++ 標准規定的行為是什么? 通常的概念是,如果new
運算符不能分配請求大小的動態內存,那么它應該拋出類型為std::bad_alloc
的異常。
然而,甚至在拋出bad_alloc
異常之前bad_alloc
發生更多的事情:
C++03 第 3.7.4.1.3 節:說
分配存儲失敗的分配函數可以調用當前安裝的 new_handler(18.4.2.2),如果有的話。 [注意:程序提供的分配函數可以使用 set_new_handler 函數 (18.4.2.3) 獲取當前安裝的 new_handler 的地址。] 如果使用空異常規范 (15.4) 聲明的分配函數 throw() 失敗分配存儲,它應返回一個空指針。 未能分配存儲的任何其他分配函數應僅通過拋出類 std::bad_alloc (18.4.2.1) 或從 std::bad_alloc 派生的類的異常來指示失敗。
考慮以下代碼示例:
#include <iostream>
#include <cstdlib>
// function to call if operator new can't allocate enough memory or error arises
void outOfMemHandler()
{
std::cerr << "Unable to satisfy request for memory\n";
std::abort();
}
int main()
{
//set the new_handler
std::set_new_handler(outOfMemHandler);
//Request huge memory size, that will cause ::operator new to fail
int *pBigDataArray = new int[100000000L];
return 0;
}
在上面的例子中, operator new
(很可能)將無法為 100,000,000 個整數分配空間,並且函數outOfMemHandler()
將被調用,並且程序將在發出錯誤消息后中止。
如這里所見,當無法滿足內存請求時, new
運算符的默認行為是重復調用new-handler
函數,直到它可以找到足夠的內存或沒有更多的新處理程序。 在上面的例子中,除非我們調用std::abort()
, outOfMemHandler()
將被重復調用。 因此,處理程序要么確保下一次分配成功,要么注冊另一個處理程序,要么不注冊處理程序,要么不返回(即終止程序)。 如果沒有新的處理程序並且分配失敗,則操作員將拋出異常。
new_handler
和set_new_handler
? new_handler
是一個指向一個函數的指針的 typedef,它不接受和返回什么,而set_new_handler
是一個接受並返回一個new_handler
。
就像是:
typedef void (*new_handler)();
new_handler set_new_handler(new_handler p) throw();
set_new_handler 的參數是一個指向函數 operator new
的指針,如果它不能分配請求的內存,它應該調用。 它的返回值是指向先前注冊的處理程序函數的指針,如果沒有先前的處理程序,則返回 null。
鑒於new
的行為,設計良好的用戶程序應該通過提供適當的new_handler
來處理內存不足的情況,它執行以下操作之一:
提供更多可用內存:這可能允許 operator new 循環內的下一次內存分配嘗試成功。 實現這一點的一種方法是在程序啟動時分配一大塊內存,然后在第一次調用 new-handler 時將其釋放以供程序使用。
安裝不同的 new-handler:如果當前的 new-handler 不能提供更多內存可用,並且有另一個 new-handler 可以,那么當前的 new-handler 可以在它的位置安裝另一個 new-handler (通過調用set_new_handler
)。 下一次 operator new 調用 new-handler 函數時,它將獲取最近安裝的函數。
(這個主題的一個變體是 new-handler 修改自己的行為,所以下次調用它時,它會做一些不同的事情。實現這一點的一種方法是讓 new-handler 修改靜態的、特定於命名空間的或影響新處理程序行為的全局數據。)
卸載新處理程序:這是通過將空指針傳遞給set_new_handler
來set_new_handler
。 如果沒有安裝新的處理程序,當內存分配不成功時, operator new
將拋出異常((可轉換為) std::bad_alloc
)。
拋出可轉換為std::bad_alloc
的異常。 此類異常不會被operator new
捕獲,但會傳播到發起內存請求的站點。
不返回:通過調用abort
或exit
。
您可以像其他任何異常一樣捕獲它:
try {
foo();
}
catch (const std::bad_alloc&) {
return -1;
}
從這一點來看,您可以做什么有用取決於您,但這在技術上絕對是可行的。
我不建議這樣做,因為bad_alloc
意味着你的內存不足。 最好是放棄而不是試圖恢復。 但是,這是您要求的解決方案:
try {
foo();
} catch ( const std::bad_alloc& e ) {
return -1;
}
我可能會為此建議一個更簡單(甚至更快)的解決方案。 如果無法分配內存, new
運算符將返回 null。
int fv() {
T* p = new (std::nothrow) T[1000000];
if (!p) return -1;
do_something(p);
delete p;
return 0;
}
我希望這會有所幫助!
讓您的foo 程序以受控方式退出:
#include <stdlib.h> /* exit, EXIT_FAILURE */
try {
foo();
} catch (const std::bad_alloc&) {
exit(EXIT_FAILURE);
}
然后編寫一個調用實際程序的shell程序。 因為地址空間是分開的,所以你的 shell 程序的狀態總是明確定義的。
當然,您可以捕獲bad_alloc
,但我認為更好的問題是首先如何阻止bad_alloc
的發生。
通常, bad_alloc
意味着內存分配出現問題 - 例如,當您內存不足時。 如果您的程序是 32 位的,那么當您嘗試分配 >4 GB 時,這已經發生了。 當我將 C 字符串復制到 QString 時,這發生在我身上。 C 字符串不是以 '\\0' 結尾的,這導致strlen
函數返回數十億的值。 然后它嘗試分配幾 GB 的 RAM,這導致了bad_alloc
。
當我不小心訪問構造函數的初始化列表中的未初始化變量時,我也看到了bad_alloc
。 我有一個帶有成員T bar
的類foo
。 在構造函數中,我想用參數中的值初始化成員:
foo::foo(T baz) // <-- mistyped: baz instead of bar
: bar(bar)
{
}
因為我輸錯了參數,構造函數用它自己初始化了 bar(所以它讀取了一個未初始化的值!)而不是參數。
valgrind 對此類錯誤非常有幫助!
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.