Содержит ли malloc() смежный блок памяти?
У меня есть код, написанный очень старым школьным программистом:-). это похоже на это
typedef struct ts_request
{
ts_request_buffer_header_def header;
char package[1];
} ts_request_def;
ts_request_def* request_buffer =
malloc(sizeof(ts_request_def) + (2 * 1024 * 1024));
программист в основном работает над концепцией переполнения буфера. Я знаю, что код выглядит изворотливым. поэтому мои вопросы:
-
Всегда ли malloc выделяет непрерывный блок памяти?. потому что в этом коде, если блоки не смежны, код будет терпеть неудачу с большим временем
-
Выполняя бесплатный (request_buffer), он освободит все байты, выделенные malloc i.e sizeof (ts_request_def) + (2 * 1024 * 1024),
или только байты размера структуры sizeof (ts_request_def)
-
Вы видите какие-либо очевидные проблемы с этим подходом, мне нужно обсудить это с моим боссом и хотелось бы указать на любые лазейки с этим подходом.
Ответы
Ответ 1
Чтобы ответить на ваши пронумерованные точки.
- Да.
- Все байты. Malloc/free не знает и не заботится о типе объекта, просто размер.
- Это, строго говоря, поведение undefined, но общий трюк, поддерживаемый многими реализациями. Ниже приведены другие альтернативы.
Последний стандарт C, ISO/IEC 9899: 1999 (неофициально C99), позволяет гибкие элементы массива.
Примером этого может быть:
int main(void)
{
struct { size_t x; char a[]; } *p;
p = malloc(sizeof *p + 100);
if (p)
{
/* You can now access up to p->a[99] safely */
}
}
Эта стандартизованная функция позволила вам избежать использования общего, но нестандартного расширения реализации, которое вы описываете в своем вопросе. Строго говоря, использование негибкого элемента массива и доступ за пределами его границ - это поведение undefined, но многие реализации документируют и поощряют его.
Кроме того, gcc позволяет массивы нулевой длины как расширение. Матрицы с нулевой длиной являются незаконными в стандартном C, но gcc представила эту функцию до того, как C99 предоставил нам гибкие элементы массива.
В ответе на комментарий, я объясню, почему сниппет ниже технически undefined. Номера разделов, которые я цитирую, относятся к C99 (ISO/IEC 9899: 1999)
struct {
char arr[1];
} *x;
x = malloc(sizeof *x + 1024);
x->arr[23] = 42;
Во-первых, 6.5.2.1 # 2 показывает, что [i] идентична (* ((a) + (i))), поэтому x- > arr [23] эквивалентно (* ((x- > arr ) + (23))). Теперь, 6.5.6 # 8 (при добавлении указателя и целого) говорит:
"Если оба операнда указателя и результат указывают на элементы одного и того же объекта массива или один за последним элементом объекта массива, оценка не должна приводить к переполнению, в противном случае поведение undefined".
По этой причине, поскольку x- > arr [23] не входит в массив, поведение undefined. Вы все еще можете подумать, что все в порядке, потому что malloc() подразумевает, что массив теперь расширен, но это не так. Информационное приложение J.2 (в котором перечислены примеры поведения undefined), дает дополнительное разъяснение с помощью примера:
Индекс массива выходит за пределы диапазона, даже если объект, по-видимому, доступен с помощью (как в выражении lvalue a [1] [7], учитывая объявление int a [4] [5]) (6.5.6).
Ответ 2
3 - Это довольно распространенный трюк для выделения динамического массива в конце структуры. Альтернативой было бы поместить указатель в структуру, а затем распределить массив по отдельности и не забывать также ее освобождать. То, что размер привязан к 2mb, кажется немного необычным, хотя.
Ответ 3
1) Да, да, или malloc завершится неудачно, если не будет достаточно большого достаточно непрерывного блока. (Ошибка с malloc вернет указатель NULL)
2) Да, будет. Распределение внутренней памяти будет отслеживать объем памяти, выделенный этим значением указателя, и освободить все его.
3) Это немного языковой взлом, и немного сомнительно об этом. Он по-прежнему подвержен переполнениям буфера, просто может нанять злоумышленников немного дольше, чтобы найти полезную нагрузку, которая вызовет его. Стоимость "защиты" также довольно внушительна (вам действительно нужно > 2 мб на буфер запросов?). Это также очень уродливо, хотя ваш босс не может оценить этот аргумент:)
Ответ 4
Это стандартный трюк C и не опасен для любого другого буфера.
Если вы пытаетесь показать своему боссу, что вы умнее "очень старого школьного программиста", этот код не относится к вам. Старая школа не обязательно плохая. Кажется, парень "старой школы" знает достаточно об управлении памятью;)
Ответ 5
Я не думаю, что существующие ответы вполне соответствуют сути этого вопроса. Вы говорите, что программист старой школы делает что-то подобное:
typedef struct ts_request
{
ts_request_buffer_header_def header;
char package[1];
} ts_request_def;
ts_request_buffer_def* request_buffer =
malloc(sizeof(ts_request_def) + (2 * 1024 * 1024));
Я думаю, что вряд ли он это сделает, потому что если бы это то, что он хотел сделать, он мог бы сделать это с помощью упрощенного эквивалентного кода, который не нуждается в каких-либо трюках;
typedef struct ts_request
{
ts_request_buffer_header_def header;
char package[2*1024*1024 + 1];
} ts_request_def;
ts_request_buffer_def* request_buffer =
malloc(sizeof(ts_request_def));
Готов поспорить, что то, что он действительно делает, это что-то вроде этого:
typedef struct ts_request
{
ts_request_buffer_header_def header;
char package[1]; // effectively package[x]
} ts_request_def;
ts_request_buffer_def* request_buffer =
malloc( sizeof(ts_request_def) + x );
То, что он хочет достичь, - это распределение запроса с переменным размером пакета x. Конечно, незаконно объявлять размер массива переменной, поэтому он обходит это с помощью трюка. Похоже, он знает, что он делает со мной, трюк хорошо подходит к почтенному и практическому концу шкалы обмана C.
Ответ 6
Что касается № 3, без лишнего кода трудно ответить. Я не вижу в этом ничего плохого, если только это не происходит. Я имею в виду, что вы не хотите выделять 2 Мб фрагментов памяти все время. Вы также не хотите делать это без необходимости, например. если вы используете только 2k.
Тот факт, что вам это не нравится по какой-то причине, недостаточно для того, чтобы возражать против него, или оправдывать его полностью переписывание. Я бы внимательно посмотрел на использование, попытался понять, что думал первоначальный программист, внимательно посмотрите на переполнение буфера (как указывал workmad3) в коде, который использует эту память.
Есть много распространенных ошибок, которые вы можете найти. Например, проверяет ли код, чтобы убедиться, что malloc() преуспел?
Ответ 7
Эксплойт (вопрос 3) действительно зависит от интерфейса к вашей структуре. В контексте это распределение может иметь смысл, и без дополнительной информации невозможно сказать, защищено ли оно или нет.
Но если вы имеете в виду проблемы с распределением памяти, большей, чем структура, это отнюдь не плохой дизайн C (я бы даже не сказал, что эта старая школа...;))
Просто последнее замечание здесь - точка с наличием char [1] заключается в том, что конечный NULL всегда будет в объявленной структуре, то есть в буфере могут быть 2 * 1024 * 1024 символа, и у вас нет для учета NULL с помощью "+1". Может показаться маленьким подвигом, но я просто хотел указать.
Ответ 8
Я часто видел и использовал этот шаблон.
Его преимущество заключается в упрощении управления памятью и, следовательно, во избежании риска утечки памяти. Все, что требуется, - это освободить блок malloc'ed. С дополнительным буфером вам понадобятся два бесплатных. Однако следует определить и использовать функцию деструктора для инкапсуляции этой операции, чтобы вы всегда могли изменить ее поведение, например, переключиться на вторичный буфер или добавить дополнительные операции, которые должны выполняться при удалении структуры.
Доступ к элементам массива также немного более эффективен, но это все меньше и меньше для современных компьютеров.
Код также будет корректно работать, если выравнивание памяти изменяется в структуре с различными компиляторами, так как это довольно часто.
Единственная потенциальная проблема, которую я вижу, заключается в том, что компилятор переставляет порядок хранения переменных-членов, поскольку этот трюк требует, чтобы поле пакета оставалось последним в хранилище. Я не знаю, запрещает ли C-подстановку замену.
Обратите также внимание, что размер выделенного буфера, скорее всего, будет больше, чем требуется, по крайней мере, на один байт с дополнительными байтами заполнения, если они есть.
Ответ 9
Да. malloc возвращает только один указатель - как он мог бы сообщить запрашивающему, что он выделил несколько несмежных блоков для удовлетворения запроса?
Ответ 10
Хотелось бы добавить, что это не так часто, но я мог бы также назвать его стандартной практикой, потому что Windows API полон такого использования.
Проверьте, например, общую структуру заголовка BITMAP.
http://msdn.microsoft.com/en-us/library/aa921550.aspx
Последний квадрат RBG представляет собой массив размером 1, который зависит именно от этой техники.
Ответ 11
В ответ на ваш третий вопрос.
free
всегда освобождает всю память, выделенную одним выстрелом.
int* i = (int*) malloc(1024*2);
free(i+1024); // gives error because the pointer 'i' is offset
free(i); // releases all the 2KB memory
Ответ 12
Этот общий трюк также объясняется в fooobar.com/questions/166801/....
Ответ 13
Ответ на вопрос 1 и 2: Да
Об уродстве (т.е. вопросе 3), что программист пытается сделать с этой выделенной памятью?
Ответ 14
вещь, чтобы понять здесь, состоит в том, что malloc
не видит вычислений, сделанных в этом
malloc(sizeof(ts_request_def) + (2 * 1024 * 1024));
Это то же самое, что и
int sz = sizeof(ts_request_def) + (2 * 1024 * 1024);
malloc(sz);
Вы можете подумать, что он выделяет 2 куска памяти, а в разуме они являются "структурой", "некоторыми буферами". Но malloc вообще этого не видит.