Pull to refresh

Нюансы выделения памяти в Си-программах под управлением GNU/Linux

В любом языке программирования начинаются проблемы, когда программа запрашивает ресурсы у операционной системы. И чем ближе язык «к истокам» тем неочевидней и опасней эти проблемы. В этой статье мы рассмотрим нюансы использования malloc() и разберёмся как им пользоваться надёжно и безопасно.

Успешный вызов malloc() возвращает блок неинициализированной памяти. Данные внутри блока остаются в том виде, в котором их оставляет система. А современные распространённые ОС никаких действий с памятью перед отдачей на неё ссылки не производят. Так что, при должном везении, там можно найти что-то полезное или интересное.

Ну, это очевидно и все об этом знают.

А вот факт, что Linux может не предоставить всего объёма запрошенной памяти и при этом вернуть не-NULL при вызове malloc() уже не такой общеизвестный. Более того – объем фактически выделяемой памяти обычно больше, чем запрашивалось. Это связано с нюансами выравнивания памяти (данные не могут находиться по произвольному адресу. Точнее могут, но это может заметно замедлить к ним обращение и такой режим необходимо включать принудительно).

А сейчас мы рассмотрим самую опасную ситуацию – когда malloc() возвращает валидный адрес, но система резервирует памяти меньше запрашиваемого. Благо решается(точнее детектируется) она парой строк кода, о которых я узнал лишь после того, как непосредственно столкнулся с проблемой.

Узнать фактический размер выделенной памяти мы можем с помощью функции malloc_usable_size(void*), которая возвращает размер блока, на который указывает указатель, передаваемый в качестве параметра. Поэтому код, защищённый от всех недоразумений, может выглядеть приблизительно так:

        size_t length = 0;
        void* c_ptr = malloc(25);
        if (c_ptr != NULL)
        {
                length = malloc_usable_size(c_ptr);
                printf("malloc_usable_size(c_ptr) == %i\n", length);
        } else
        {
                printf("Memory allocation error\n");
        }

Для Си++ ситуация выглядит совершенно идентично:

        char* cpp_ptr = new char[25];
        if (cpp_ptr != NULL)
        {
                length = malloc_usable_size(cpp_ptr);
                std::cout << "malloc_usable_size(cpp_ptr) == " << length << std::endl;
        } else
        {
                std::cout << "Memory allocation error" << std::endl;
        }

Обращаю внимание, что передаваемый указатель должен указывать на начало выделенного блока, а не на любой адрес внутри блока. Только начало!

Пока я искал нужную информацию, я наткнулся на Windows-аналог malloc_usable_size(void*):

size_t _msize( void *memblock );

Не знаю, правда, насколько эта функция стандартна и распространена и как она вообще работает – возможности проверить у меня нет. Будьте бдительны — иногда ломаются вещи, которые в принципе не могут ломаться.
Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.