簡體   English   中英

如何在 C++ 中處理 bad_alloc?

[英]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表示由於沒有足夠的可用內存而無法分配資源。 在大多數情況下,您的程序無法應對這種情況,並且盡快終止是唯一有意義的行為。

更糟糕的是,現代操作系統經常過度分配:在這樣的系統上,即使沒有足夠的可用內存, mallocnew也可以返回一個有效的指針std::bad_alloc永遠不會被拋出,或者至少不是一個可靠的標志內存耗盡。 相反,嘗試訪問分配的內存將導致無法捕獲的段錯誤(您可以處理段錯誤信號,但之后無法恢復程序)。

捕獲std::bad_alloc時你唯一能做的就是記錄錯誤,並嘗試通過釋放未完成的資源來確保安全的程序終止(但這是在錯誤被拋出后的正常堆棧展開過程中自動完成的,如果該程序適當地使用了 RAII)。

在某些情況下,程序可能會嘗試釋放一些內存並重試,或者使用輔助內存(= 磁盤)而不是 RAM,但這些機會僅存在於具有嚴格條件的非常特定的場景中:

  1. 應用程序必須確保它運行在一個不會過度使用內存的系統上,即它在分配時而不是稍后發出失敗信號。
  2. 應用程序必須能夠立即釋放內存,在此期間沒有任何進一步的意外分配。

應用程序控制第 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的應用程序,你讀這個答案,請拍我的電子郵件,我真的很好奇你的情況。

C++ 中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_handlerset_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。

如何處理 C++ 中的內存不足情況?

鑒於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_handlerset_new_handler 如果沒有安裝新的處理程序,當內存分配不成功時, operator new將拋出異常((可轉換為) std::bad_alloc )。

拋出可轉換為std::bad_alloc的異常 此類異常不會被operator new捕獲,但會傳播到發起內存請求的站點。

不返回:通過調用abortexit

您可以像其他任何異常一樣捕獲它:

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM