Должен ли указатель, переданный в free(), указывать на начало блока памяти или он может указывать на его внутреннюю часть?

Вопрос в заголовке... Я искал, но ничего не нашел.


Редактировать:

Я действительно не вижу необходимости объяснять это, но поскольку люди думают, что то, что я говорю, не имеет смысла (и что я задаю неправильные вопросы), вот в чем проблема:

Поскольку люди, кажется, очень интересуются «коренной» причиной всех проблем, а не фактическим заданным вопросом (поскольку это, по-видимому, помогает решить проблему лучше, давайте посмотрим, так ли это), вот проблема :

Я пытаюсь создать библиотеку времени выполнения D на основе NTDLL.dll, чтобы использовать эту библиотеку для подсистем, отличных от подсистемы Win32. Так что это заставляет меня связываться только с NTDLL.dll.

Да, я знаю, что функции "недокументированы" и могут измениться в любое время (хотя я готов поспорить на сотню долларов, что wcstombs по-прежнему будет делать то же самое точно через 20 лет, если он еще существует). Да, я знаю, что люди (особенно Microsoft) не любят, когда разработчики ссылаются на эту библиотеку, и что меня, вероятно, тут же подвергнут критике. И да, эти два пункта выше означают, что такие программы, как chkdsk и дефрагментаторы, которые запускаются до подсистемы Win32, вообще не должны создаваться, потому что буквально невозможно скомпоновать что-либо вроде kernel32. .dll или msvcrt.dll и по-прежнему иметь собственные исполняемые файлы NT, поэтому мы, разработчики, должны просто притворяться, что эти этапы должны навсегда остаться за пределами нашей досягаемости.

Но нет, я сомневаюсь, что кто-нибудь из присутствующих захочет, чтобы я вставил несколько тысяч строк кода и помог мне просмотреть их и попытаться выяснить, почему выделение памяти, которое не вызывает сбоев, отклоняется исходный код, который я изменяю. Вот почему я спросил о проблеме, отличной от «коренной» причины, хотя это предположительно считается лучшей практикой сообщества.

Если что-то по-прежнему не имеет смысла, не стесняйтесь оставлять комментарии ниже! :)


Редактировать 2:

Примерно через 8 часов отладки я наконец нашел проблему:

Оказывается, RtlReAllocateHeap() не автоматически работает как RtlAllocateHeap(), если переданный ему указатель равен NULL.


person user541686    schedule 03.01.2011    source источник
comment
Просто чтобы завершить другие ответы. malloc() хранит запись о выделенных им блоках, и при освобождении() чего-либо он сравнивает указатель с этой записью, чтобы проверить, был ли он выделен.   -  person BlackBear    schedule 04.01.2011
comment
@BlackBear: Да, вот почему это заставило меня задуматься, почему он не может просто проверить, находится ли указатель внутри блока (насколько это сложно??)...   -  person user541686    schedule 04.01.2011
comment
@BlackBear: На самом деле, в большинстве реализаций куча содержит информацию только о свободных блоках. Данные, требуемые функцией free() (например, размер блока), добавляются функцией malloc() к выделенному блоку, размер записи вычитается из указателя, переданного функции free() для доступа к записи. Если реализация включает проверочную подпись, возможно обнаружение ошибок, в противном случае недопустимая запись приведет к повреждению кучи.   -  person Clifford    schedule 04.01.2011
comment
@Clifford: Ах, хорошо, это имеет смысл. Спасибо.   -  person user541686    schedule 04.01.2011
comment
Ой: RtlReAllocateHeap() не обрабатывает указатель NULL? Жаль, что MS не заставила это вести себя как стандартную функцию realloc(), тем более, что она занимает всего пару строк кода. Это трудный урок.   -  person Michael Burr    schedule 04.01.2011
comment
Да, я полностью предположил, что он принял это и работал как задумано, затем потратил 8 (или больше? без понятия) часов на его отладку, и я наконец узнал, только когда сравнил printf() со стороны подключенного realloc(). рядом и заметил, что один из моих отпечатков тормозится. :\   -  person user541686    schedule 04.01.2011


Ответы (4)


Он должен указывать на начало блока. Безопасно передавать нулевой указатель на free(), но передача любого указателя, не выделенного malloc() или одним из его родственников, приведет к неопределенному поведению. Некоторые системы дадут вам ошибку времени выполнения - что-то вроде "Освобождение указателя, не распределенного".

Редактировать:

Я написал тестовую программу, чтобы получить сообщение об ошибке. На моей машине эта программа:

#include <stdlib.h>

int main(int argc, char **argv)
{
  int *x = malloc(12);
  x++;

  free(x);

  return 0;
}

Вылетает с этим сообщением:

app(31550) malloc: *** error for object 0x100100084: pointer being freed was not allocated
person Carl Norum    schedule 03.01.2011
comment
Ха... в таком случае, есть ли способ (переносимо) запросить память, которая, скажем, выровнена по 64 байтам? Я собирался выделить больше и извлечь кусок, но так как я перехожу по указателю, я не могу уследить за размером неиспользованных байтов в начале... - person user541686; 04.01.2011
comment
@ Ламберт, конечно, можешь. Написание «выровненного malloc» на самом деле довольно популярный вопрос на собеседовании. Просто выделите достаточно места для вашего выравнивания, а также дополнительное место для указателя. Затем спрячьте указатель прямо перед выровненной памятью и напишите функцию free(), которая извлекает эту информацию и освобождает «настоящий» указатель. - person Carl Norum; 04.01.2011
comment
Ну, дело не в том, что я не могу, а в том, что мне придется создать совершенно новую таблицу и следить за тем, что внутри нее... и хотя я знаю, как это сделать, это просто боль Я хотел бы избежать, если я могу. :) - person user541686; 04.01.2011
comment
Это не так сложно. Но вы также можете использовать memalign() и друзей. Проверьте этот ответ: stackoverflow.com/questions/3839922/aligned-malloc-in-gcc - person Carl Norum; 04.01.2011
comment
@Santiago: Но что происходит, библиотека дает мне указатель на любое место внутри региона, чтобы я мог его освободить... так как это поможет? - person user541686; 04.01.2011
comment
Вы можете выделить немного больше в любом случае (даже если это не требуется) и записать некоторые данные перед передающим указателем, чтобы вы могли получить к ним доступ как *(x - 1). (Не очень надежный, да). - person crazylammer; 04.01.2011
comment
@Carl: Спасибо за ссылку, но я работаю под Windows и использую NTDLL.dll, а этой функции не существует. Но +1 за ваш ответ! :) - person user541686; 04.01.2011
comment
@Lambert, тогда тебе, возможно, придется написать свой собственный. Однако вполне возможно, что в Windows есть похожие процедуры. - person Carl Norum; 04.01.2011
comment
@Carl: Хорошо, это не тот ответ, на который я надеялся, но тем не менее правильный. Спасибо! :) - person user541686; 04.01.2011
comment
@Carl: Часть, которая должна дать вам ошибку времени выполнения, вводит в заблуждение. Передача внутреннего указателя на free приводит к неопределенному поведению, скорее всего, серьезному повреждению памяти. - person R.. GitHub STOP HELPING ICE; 04.01.2011
comment
@Р. Я немного подправил формулировку. - person Carl Norum; 04.01.2011
comment
должен выдавать ошибку времени выполнения не является требуемым поведением; результаты не определены и зависят от реализации. В некоторых случаях это просто испортит кучу, добавив недопустимый блок, вы можете не заметить, пока не произойдет последующее выделение памяти, когда происходит дерьмо! - person Clifford; 04.01.2011
comment
@lambert В Windows есть _aligned_malloc() , msdn.microsoft. com/en-us/library/8z34s9c6%28VS.80%29.aspx - person nos; 04.01.2011
comment
@nos: Windows — это 4 ГБ данных, включая MSVCRT.dll. NTDLL.dll — это библиотека. И NTDLL.dll (это единственная библиотека, на которую я ссылаюсь) не имеет malloc или _aligned_malloc, так что это не помогает. Спасибо за ответ. - person user541686; 04.01.2011

Единственное, что вы можете передать в free, — это указатель, возвращенный вам из malloc (или calloc, realloc и т. д.), или NULL.

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

person Andy Lester    schedule 03.01.2011
comment
Ха-ха, дело не в том, что я не хочу заморачиваться, просто в моей ситуации это сделать не так-то просто. Но спасибо за ответ. :) - person user541686; 04.01.2011
comment
Как это не просто сделать? Вы вызываете какую-то функцию API, которая не возвращает вам этот указатель malloced? Если он специально скрывает это, вы должны оставить функцию API для управления памятью. Будьте осторожны, потому что ошибки с двойным освобождением ужасно раздражают, потому что вы должны выяснить, кто освободил вас до вас и почему. - person Andy Lester; 04.01.2011
comment
Ха-ха-ха, нет, я пытаюсь реализовать выровненный malloc поверх RtlAllocateHeap NTDLL, чтобы передать его в другую библиотеку. Я мог бы создать новую таблицу, чтобы отслеживать все, но тогда мне пришлось бы беспокоиться о многопоточности и т. д., и все было бы мучительно. - person user541686; 04.01.2011
comment
@Lambert - IOW, ты не хочешь беспокоиться. Не стыдись этого. Желание избегать ненужной работы — хорошее качество для разработчика программного обеспечения. Особенно, если вы готовы заранее поработать для достижения этой цели. Только не доводите до крайностей. - person T.E.D.; 04.01.2011
comment
@ Ламберт, не делай стол. Просто добавьте заголовок в блок памяти в выровненной реализации malloc. - person Carl Norum; 04.01.2011
comment
@Carl: Я просто подумал о том же и пришел сюда, чтобы написать это, и вуаля, ты тоже это написал! :) это просто немного сложно, так как, подумав об этом, кажется, что мне нужно всегда вычитать 2 * alignment - 1 из указателя, который дает мне пользователь, выравнивать его по следующей границе и сохранять размер там непользовательский буфер, поскольку, если я просто вычту alignment - 1, перед буфером может не остаться места. Спасибо за предложение, +1. - person user541686; 04.01.2011

Из комментариев, которые вы добавили к существующим ответам, кажется, что вы задаете неправильный вопрос. Если вам нужно выравнивание памяти, почему бы вам не задать этот вопрос? Спрашивайте о корне проблемы, а не о своем предполагаемом решении!

Итак, если вы не возражаете, я отвечу на вопрос, который вы должны были задать:

Выровненное выделение памяти поддерживается в Win32 с помощью _aligned_malloc(). Это более или менее эквивалентно POSIX memalign()

Если вам нужна реализация выровненного распределения, это довольно просто реализовать:

void* aligned_malloc( size_t size, size_t alignment )
{
    void* unaligned = malloc( size + alignment ) ;
    void* aligned = (void*)(((intptr_t)unaligned + alignment) & ~(alignment - 1));
    void** free_rec_ptr = ((void**)aligned - 1) ;
    *free_rec_ptr = unaligned ;

    return aligned ;
}

void aligned_free( void* block )
{
    void** free_rec_ptr = ((void**)block - 1) ;
    free( *free_rec_ptr ) ;
}

Аргумент alignment должен быть степенью двойки.

person Clifford    schedule 03.01.2011
comment
@Clifford: Спасибо за ваш ответ, но, к сожалению, я уже знал о _aligned_malloc - его просто нет в NTDLL.dll (как и malloc), единственной библиотеке, на которую я ссылаюсь, и мне просто интересно, если я даже нужно было возиться со всей этой проблемой отслеживания выравнивания указателей, которые я передаю в другие библиотеки, если мне это просто не нужно. Так что нет, я не избегал вопроса об первопричине, я действительно действительно хотел задать этот вопрос. - person user541686; 04.01.2011
comment
@Lambert: это часть библиотеки MSVCRT, которая обычно связана неявно. Так как malloc()/free() также находятся в этой библиотеке, вы должны иметь к ней доступ. Какой компилятор вы используете? - person Clifford; 04.01.2011
comment
@Clifford: я специально пытаюсь не ссылаться на MSVCRT по разным причинам, поэтому по определению эта функция недоступна. Это не проблема компилятора, это то, что я сделал специально. - person user541686; 04.01.2011
comment
@Lambert: Вы не имеете смысла: если вы можете вызвать free(), вы связали MSVCRT! Причина, по которой я спрашиваю о компиляторе, заключается в том, что MSDN документирует только _aligned_malloc() для VC++ 2003 и более поздних версий. Я не уверен, что он доступен в более ранних версиях. Было бы лучше, если бы вы отвечали на вопросы, связанные с расследованием, а не предполагали, что вопрос не имеет отношения к делу. - person Clifford; 04.01.2011
comment
@Clifford: Я все понимаю - это ваше неверное предположение, что я на самом деле звоню free(). Я подаю free() для вызывающего абонента и упаковываю RtlFreeHeap(). Имеет ли это смысл сейчас? Кроме того, я знаю, что не связываюсь с MSVCRT, потому что я дизассемблировал свой исполняемый файл. Поэтому, когда я говорю, что не ссылаюсь на это, я не шучу, я действительно имею это в виду. :) - person user541686; 04.01.2011
comment
@Lambert: Учитывая вопрос, вряд ли это было несправедливым предположением! Если вы предоставляете свою собственную реализацию free(), то вся предпосылка вашего вопроса ошибочна! Если это ваша реализация free(), то только вы знаете ответ! Документация для RtlFreeHeap даже понятнее, чем для free() — это указатель, возвращенный RtlAllocateHeap, а не какой-то другой указатель. - person Clifford; 04.01.2011
comment
@Clifford: я создаю free() как оболочку для RtlFreeHeap(), поэтому на самом деле я не пишу free() с нуля. Не вижу в этом недостатка. См. редактирование в моем вопросе, и если вы все еще считаете, что это не имеет смысла, дайте мне знать; благодаря. - person user541686; 04.01.2011
comment
@Lambert: Да, но поведение определяется RtlHeapFree(), что может не совпадать с поведением free(). Так оно и есть, но скажем, стандарт делал определение free() для работы, как вы предложили; это не означает, что RtlHeapFree() будет работать так же. - person Clifford; 04.01.2011
comment
@Clifford: этот пост был связан с моя предыдущая запись... поэтому вопрос в том, (отредактировано) соответствует ли Heap API всем требованиям для API распределения памяти C? (Ответ: по-видимому, нет, но я не могу найти разницу... Я пытался запрограммировать оболочку и найти разницу, отсюда и этот пост.) - person user541686; 04.01.2011
comment
@Clifford: Если вам интересно, вот другой способ объяснить проблему: я модифицирую сборщик мусора, который использует malloc(), чтобы использовать RtlAllocateHeap(). RtlAllocateHeap() работает нормально, но после нескольких выделений сборщик мусора сообщает мне, что не может выделить память, несмотря на то, что памяти нет, и хотя я полностью правильно обернул все вызовы. (Мой код не связан с какой-либо функцией памяти C.) Я подумал, что, возможно, GC требует более строгого выравнивания, поэтому я попытался заставить malloc() возвращать память, которая была бы более выровнена... отсюда и этот пост . Имеет ли это смысл? - person user541686; 04.01.2011
comment
@Lambert: Почему-то я сомневаюсь, что проблема в выравнивании, но если вам это нужно, возможно, пример реализации в моем редактировании поможет. При необходимости используйте существующие оболочки malloc/free или замените мои вызовы malloc/free функциями Rtl. Я попытался сохранить общий ответ, соответствующий вашему первоначальному вопросу, а не последующим деталям. - person Clifford; 04.01.2011
comment
@Clifford: Большое спасибо за ваше редактирование. На самом деле я сам экспериментировал с чем-то подобным, но похоже, как вы говорите, проблема не в выравнивании (хотя сейчас это очень сбивает с толку). Однако я хотел бы отметить кое-что: ваша реализация в некоторых случаях не работает: если malloc() дает вам адрес 7, вы пишете указатель на адрес 4, который вам не принадлежит. . Я думаю, что позаботился об этом в своем коде, хотя... кажется, проблема в чем-то еще, я понятия не имею, в чем. :\ - person user541686; 04.01.2011
comment
@Clifford: Нет, проблема не в выравнивании ... Я заставил свой код возвращать 256-байтовый выровненный буфер, но он все еще не работает. Это становится интересным... Мне нужно подключиться к malloc() из CRT, чтобы увидеть, как он ведет себя по-другому! - person user541686; 04.01.2011
comment
@Clifford: Оказывается, realloc() моего кода должен был сначала выполнить нулевую проверку и вызвать malloc() вместо вызова RtlReAllocateHeap(), поскольку они ведут себя по-разному, если указатель равен нулю. В любом случае спасибо за вашу помощь! :) - person user541686; 04.01.2011
comment
@Lambert: Это позор, я был особенно доволен своей реализацией alligned_malloc()! ;) Кстати, я считаю, что это работает; если malloc() дал вам адрес 7 и выравнивание было 64, указатель записывается как 60, а не 4: `(7 + 64) & ~(64 - 1) == 0x47 & ~0x3f == 0x47 & 0xFFFFFFFC0 == 0x40 == 60' - person Clifford; 04.01.2011
comment
@Clifford: Ха-ха, да, это позор. :\ Хм... Итак, скажем, реализация дала вам адрес 63 -- тогда вы бы округлили до 64, вычли 4, а затем записали значение по адресу 60, верно? - person user541686; 04.01.2011
comment
@Lambert: Вы правы, это не совсем правильно, более чем одним способом. Идея состоит в том, что адрес 63 будет выравнен до 128, а адрес записан до 124. Выражение не работает в этом случае, и даже если бы это произошло, перераспределение 64 слишком мало, оно должно быть в два раза больше выравнивания для худший случай. Вероятно, стоит исправить на всякий случай, если кто-то придет и попытается его использовать! Однако мне придется вернуться к нему. - person Clifford; 05.01.2011
comment
Да, как я это сделал: я выделяю 4 * align больше, чем мне нужно, при вызове RtlAllocateHeap (на всякий случай); затем я беру полученный указатель ptr, добавляю к нему align, округляю вверх до следующей границы -- это пользовательский буфер, p. Затем перейдите вниз на один align и там запишите (используя всего 1 байт, предполагая align < 256) значение p - ptr. Затем при освобождении указателя сделайте обратное, чтобы найти реальное начало, и передайте его в RtlFreeHeap. (Я не обрабатывал align >= 256, но в этом случае я мог бы использовать ushort, просто убедившись, что align никогда не будет < 2.) - person user541686; 05.01.2011

FWIW, в последний раз, когда я изучал, как библиотека времени выполнения C работает с malloc и free, блок, который дает вам malloc, на самом деле имеет несколько дополнительных слов с отрицательным смещением относительно указателя, который он вам дает. Они говорят такие вещи, как размер блока и другие вещи. free полагался на возможность найти эти вещи. Не знаю, проливает ли это свет.

person Mike Dunlavey    schedule 05.01.2011
comment
Это именно то, что я в итоге сделал, за исключением того, что это усложняется с разными выравниваниями. Но спасибо за ответ. - person user541686; 05.01.2011