Какова область применения и полезность bad_alloc в Linux? - PullRequest
1 голос
/ 22 мая 2019

Долгое время я думал, что C ++ (STL) будет выбрасывать bad_alloc, когда не было доступной памяти.

Однако, руководствуясь некоторыми общими знаниями, которые я слышал о Linux (например, «Linux на самом деле не резервирует память, пока вы ее не используете»), я решил проверить, как это повлияло на поведение bad_alloc. Оказывается, есть определенно определенные применения, в которых bad_alloc не генерируется, потому что фактическая ошибка происходит после того, как allocator выполнил свою работу.

В этом примере в первом цикле я выделяю явно больше памяти (1 ТБ) чем то, что у меня есть в моей системе Linux Fedora30.

Этот цикл заканчивается, и следующий цикл выполняется примерно до тех пор, пока я не инициализирую (создаю) около 100 ГБ (= общий объем ОЗУ + обмен в моей системе).

#include<iostream>
#include<memory>
#include<cassert>
#include<vector>

using T = char;

int main(){
    std::size_t block_size = 1000000000; // ~1GB
    std::size_t n_blocks = 1000; // number of blocks
    std::allocator<T> A;
    std::vector<T*> ps(n_block);
    for(int i = 0; i != n_block; ++i){
        cout << "allocating block " << i << std::endl;
        ps[i] = A.allocate(block_size); // ps[i] = (char*)malloc(1000000000);
        assert(ps[i]);
    }
    for(int i = 0; i != n_block; ++i){
        cout << "constructing block " << i << std::endl;
        for(long j = 0; j != block_size; ++j){
            A.construct(ps[i] + j, 'z'); // ps[i][j] = 'z'; // hard error "Killed" HERE
        }
    }
    //////////////////////////////// interesting part ends here
    for(int i = 0; i != n_block; ++i){
        for(long j = 0; j != block_size; ++j){
            assert(ps[i][j] == 'z');
            A.destroy(ps[i]);
        }
        A.deallocate(ps[i], block_size);
    }
}

Я понимаю, что в операционных системах существуют идиосинкразии и что во многих операциях, связанных с системой, наблюдается неопределенное поведение.

У меня вопрос: правильно ли я использую C ++? Нужно ли что-то делать с этим поведением с точки зрения восстановления ожидаемого поведения bad_alloc? Даже если нет, то есть ли способ обнаружить, что к какой-то памяти нельзя прикоснуться заранее? Проверка на null, кажется, не покрывает этот случай (опять же, в Linux.)

Когда я строил этот пример, я думал, что , возможно, , construct (то есть размещение new внутри него) как-то сгенерирует или даст какую-то ошибку при обнаружении чего-то подозрительного в расположении необработанного указателя. но этого не произошло: Linux просто «убил» программу.

Это вывод этой программы в Fedora30 с подкачкой 32 ГБ + 64 ГБ:

1
2
3
...
997
998
999
*0
*1
*2
*3
...
*86
*87
*88
*89
*90
Killed

(Linux выводит «Killed» и выходит из программы).


Примечание: я знаю другие варианты использования (например, размеры блоков, другой порядок - чередование - размещение и построение и т. Д.), В которых программа выдает bad_alloc. Я спрашиваю конкретно об использовании, подобном этому, и есть ли способ восстановления в этом контексте.

Например, я знаю, что если я сделаю A.allocate("1TB"), это сразу выбросит bad_alloc. Это также произойдет изящно, если я чередую выделение конструкции из небольших блоков:

    for(int i = 0; i != n_block; ++i){
        ps[i] = A.allocate(block_size); //eventually throws, but HERE
        for(long j = 0; j != block_size; ++j){
            A.construct(ps[i] + j, 'z');
        }
    }
...