Как стать автором
Обновить

Нюансы выделения памяти в Си-программах под управлением 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 );

Не знаю, правда, насколько эта функция стандартна и распространена и как она вообще работает – возможности проверить у меня нет. Будьте бдительны — иногда ломаются вещи, которые в принципе не могут ломаться.
Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.