Нюансы выделения памяти в Си-программах под управлением GNU/Linux
Invite pending
В любом языке программирования начинаются проблемы, когда программа запрашивает ресурсы у операционной системы. И чем ближе язык «к истокам» тем неочевидней и опасней эти проблемы. В этой статье мы рассмотрим нюансы использования malloc() и разберёмся как им пользоваться надёжно и безопасно.
Успешный вызов malloc() возвращает блок неинициализированной памяти. Данные внутри блока остаются в том виде, в котором их оставляет система. А современные распространённые ОС никаких действий с памятью перед отдачей на неё ссылки не производят. Так что, при должном везении, там можно найти что-то полезное или интересное.
Ну, это очевидно и все об этом знают.
А вот факт, что Linux может не предоставить всего объёма запрошенной памяти и при этом вернуть не-NULL при вызове malloc() уже не такой общеизвестный. Более того – объем фактически выделяемой памяти обычно больше, чем запрашивалось. Это связано с нюансами выравнивания памяти (данные не могут находиться по произвольному адресу. Точнее могут, но это может заметно замедлить к ним обращение и такой режим необходимо включать принудительно).
А сейчас мы рассмотрим самую опасную ситуацию – когда malloc() возвращает валидный адрес, но система резервирует памяти меньше запрашиваемого. Благо решается(точнее детектируется) она парой строк кода, о которых я узнал лишь после того, как непосредственно столкнулся с проблемой.
Узнать фактический размер выделенной памяти мы можем с помощью функции malloc_usable_size(void*), которая возвращает размер блока, на который указывает указатель, передаваемый в качестве параметра. Поэтому код, защищённый от всех недоразумений, может выглядеть приблизительно так:
Для Си++ ситуация выглядит совершенно идентично:
Обращаю внимание, что передаваемый указатель должен указывать на начало выделенного блока, а не на любой адрес внутри блока. Только начало!
Пока я искал нужную информацию, я наткнулся на Windows-аналог malloc_usable_size(void*):
Не знаю, правда, насколько эта функция стандартна и распространена и как она вообще работает – возможности проверить у меня нет. Будьте бдительны — иногда ломаются вещи, которые в принципе не могут ломаться.
Успешный вызов 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 );
Не знаю, правда, насколько эта функция стандартна и распространена и как она вообще работает – возможности проверить у меня нет. Будьте бдительны — иногда ломаются вещи, которые в принципе не могут ломаться.