Pergunta

Existe um método chamado foo que às vezes retorna o seguinte erro:

terminate called after throwing an instance of 'std::bad_alloc'
  what():  std::bad_alloc
Abort

Existe uma maneira que eu possa usar um try-catch bloco para impedir que esse erro termine meu programa (tudo o que quero fazer é retornar -1)?

Se sim, qual é a sintaxe para isso?

De que outra forma posso lidar com bad_alloc em C++?

Foi útil?

Solução

Você pode capturá-lo como qualquer outra exceção:

try {
  foo();
}
catch (const std::bad_alloc&) {
  return -1;
}

O que você pode fazer de forma útil a partir deste ponto depende de você, mas é definitivamente viável tecnicamente.

Outras dicas

Em geral você não pode, e não deveria tentar, para responder a esse erro. bad_alloc indica que um recurso não pode ser alocado porque não há memória suficiente disponível.Na maioria dos cenários, seu programa não consegue lidar com isso, e encerrar logo é o único comportamento significativo.

Pior ainda, os sistemas operacionais modernos geralmente alocam em excesso:em tais sistemas, malloc e new pode um ponteiro válido mesmo se não houver memória livre suficiente - std::bad_alloc nunca será lançado ou, pelo menos, não é um sinal confiável de esgotamento da memória.Em vez disso, tentativas de acesso a memória alocada resultará em uma falha de segmentação, que não é capturável (você pode lidar o sinal de falha de segmentação, mas não será possível retomar o programa posteriormente).

A única coisa que você poderia fazer ao capturar std::bad_alloc é talvez registrar o erro e tentar garantir o encerramento seguro do programa, liberando recursos pendentes (mas isso é feito automaticamente no curso normal do desenrolamento da pilha após o erro ser gerado se o programa usar RAII adequadamente).

Em certos casos, o programa pode tentar liberar alguma memória e tentar novamente, ou usar memória secundária (= disco) em vez de RAM, mas essas oportunidades só existem em cenários muito específicos com condições estritas:

  1. O aplicativo deve garantir que ele seja executado em um sistema que não sobrecarregue memória, ou sejasinaliza falha no momento da alocação e não mais tarde.
  2. O aplicativo deve ser capaz de liberar memória imediatamente, sem quaisquer outras alocações acidentais entretanto.

É extremamente raro que os aplicativos tenham controle sobre o ponto 1: aplicativos do espaço do usuário nunca fazer, é uma configuração de todo o sistema que requer permissões de root para ser alterada.1

OK, então vamos supor que você corrigiu o ponto 1.O que você pode fazer agora é, por exemplo, usar um Cache LRU para alguns dos seus dados (provavelmente alguns objetos de negócios particularmente grandes que podem ser regenerados ou recarregados sob demanda).Em seguida, você precisa colocar a lógica real que pode falhar em uma função que suporta novas tentativas. Em outras palavras, se ela for abortada, basta reiniciá-la:

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.

Mas mesmo aqui, usando std::set_new_handler em vez de lidar std::bad_alloc oferece o mesmo benefício e seria muito mais simples.


1 Se você estiver criando um aplicativo que faz ponto de controle 1, e você está lendo esta resposta, envie-me um e-mail, estou genuinamente curioso sobre suas circunstâncias.

Qual é o comportamento especificado pelo padrão C++ de new em c++?

A noção usual é que se new operador não pode alocar memória dinâmica do tamanho solicitado, então ele deve lançar uma exceção do tipo std::bad_alloc.
No entanto, algo mais acontece antes mesmo de um bad_alloc exceção é lançada:

C++ 03 Seção 3.7.4.1.3: diz

Uma função de alocação que não consegue alocar armazenamento pode invocar o new_handler(18.4.2.2) atualmente instalado, se houver.[Observação:Uma função de alocação fornecida pelo programa pode obter o endereço do new_handler atualmente instalado usando a função set_new_handler (18.4.2.3).] Se uma função de alocação declarada com uma especificação de exceção vazia (15.4), throw(), falhar ao alocar armazenamento, ele deve retornar um ponteiro nulo.Qualquer outra função de alocação que falhe na alocação de armazenamento deve apenas indicar falha lançando uma exceção da classe std::bad_alloc (18.4.2.1) ou uma classe derivada de std::bad_alloc.

Considere o seguinte exemplo de código:

#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;
}

No exemplo acima, operator new (provavelmente) não conseguirá alocar espaço para 100.000.000 inteiros, e a função outOfMemHandler() será chamado e o programa será abortado após emitindo uma mensagem de erro.

Como visto aqui, o comportamento padrão de new operador quando não for possível atender a uma solicitação de memória, é chamar o new-handler funciona repetidamente até encontrar memória suficiente ou não haver mais novos manipuladores.No exemplo acima, a menos que chamemos std::abort(), outOfMemHandler() seria chamado repetidamente.Portanto, o manipulador deve garantir que a próxima alocação seja bem-sucedida, ou registrar outro manipulador, ou não registrar nenhum manipulador, ou não retornar (ou seja,encerrar o programa).Se não houver nenhum novo manipulador e a alocação falhar, o operador lançará uma exceção.

O que é new_handler e set_new_handler?

new_handler é um typedef para um ponteiro para uma função que não recebe e não retorna nada, e set_new_handler é uma função que recebe e retorna um new_handler.

Algo como:

typedef void (*new_handler)();
new_handler set_new_handler(new_handler p) throw();

O parâmetro set_new_handler é um ponteiro para o operador de função new deve ligar se não puder alocar a memória solicitada.Seu valor de retorno é um ponteiro para a função manipuladora registrada anteriormente ou nulo se não houve manipulador anterior.

Como lidar com condições de falta de memória em C++?

Dado o comportamento de newum programa de usuário bem projetado deve lidar com condições de falta de memória, fornecendo uma resposta adequada new_handlerque faz um dos seguintes:

Disponibilize mais memória: Isso pode permitir que a próxima tentativa de alocação de memória dentro do loop do operador new seja bem-sucedida.Uma maneira de implementar isso é alocar um grande bloco de memória na inicialização do programa e, em seguida, liberá-lo para uso no programa na primeira vez que o novo manipulador for invocado.

Instale um novo manipulador diferente: Se o novo manipulador atual não puder disponibilizar mais memória, e houver outro novo manipulador que possa, então o novo manipulador atual poderá instalar o outro novo manipulador em seu lugar (chamando set_new_handler).Na próxima vez que o operador new chamar a função new-handler, ele instalará aquela instalada mais recentemente.

(Uma variação deste tema é um novo manipulador modificar seu próprio comportamento, de modo que na próxima vez que for invocado, ele faça algo diferente.Uma maneira de conseguir isso é fazer com que o novo manipulador modifique dados estáticos, específicos do namespace ou globais que afetam o comportamento do novo manipulador.)

Desinstale o novo manipulador: Isso é feito passando um ponteiro nulo para set_new_handler.Sem nenhum novo manipulador instalado, operator new lançará uma exceção ((conversível para) std::bad_alloc) quando a alocação de memória não for bem-sucedida.

Lançar uma exceção conversível para std::bad_alloc.Tais exceções não são capturadas por operator new, mas será propagado para o site que originou a solicitação de memória.

Não retornar: Ao ligar abort ou exit.

Eu não sugeriria isso, pois bad_alloc significa que você é fora da memória.Seria melhor simplesmente desistir em vez de tentar se recuperar.No entanto, aqui está a solução que você está pedindo:

try {
    foo();
} catch ( const std::bad_alloc& e ) {
    return -1;
}

Posso sugerir uma solução mais simples (e ainda mais rápida) para isso. new operador retornaria nulo se a memória não pudesse ser alocada.

int fv() {
    T* p = new (std::nothrow) T[1000000];
    if (!p) return -1;
    do_something(p);
    delete p;
    return 0;
}

Espero que isso possa ajudar!

Deixe sua programa foo saída de forma controlada:

#include <stdlib.h>     /* exit, EXIT_FAILURE */

try {
    foo();
} catch (const std::bad_alloc&) {
    exit(EXIT_FAILURE);
}

Então escreva um programa shell que chama o programa real.Como os espaços de endereço são separados, o estado do seu programa shell está sempre bem definido.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top