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

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

void initializeAndFree(int* anArray, size_t length); int main(){ size_t arrayLength = 0; scanf("%d", &arrayLength); int* myArray = (int*)malloc(sizeof(int)*arrayLength); initializeAndFree(myArray, arrayLength); } void initializeAndFree(int* anArray, size_t length){ int i = 0; for (i = 0; i < length; i++) { anArray[i] = 0; } free(anArray); } 

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

Где free() получить свое бесплатное (har-har) знание?

Помимо правильного указания Клацко, что стандарт не предусматривает этого, реальные malloc / free реализаций часто выделяют больше места, чем вы просите. Например, если вы запрашиваете 12 байт, это может обеспечить 16 (см . Алокатор памяти , который отмечает, что 16 – общий размер). Поэтому вам не нужно знать, что вы попросили 12 байт, просто он дал вам 16-байтовый кусок.

Вы не можете получить его, потому что комитет C не требовал этого в стандарте.

Если вы хотите написать непереносимый код, вам может быть повезло:

 *((size_t *)ptr - 1) 

или, может быть:

 *((size_t *)ptr - 2) 

Но будет ли это работать, будет зависеть именно от того, где реализация malloc вы используете магазины, данные.

Хотя возможно получить метаданные, которые распределитель памяти размещает перед выделенным блоком, это будет работать, только если указатель действительно является указателем на динамически выделенный блок. Это серьезно повлияло бы на полезность функции, требующей, чтобы все переданные аргументы были указателями на такие блоки, а не просто простым или статическим массивом.

Дело в том, что нет портативного способа проверки указателя, чтобы узнать, на какой тип памяти он указывает. Поэтому, хотя это интересная идея, это не особо безопасное предложение.

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

 typedef tSizedAlloc { size_t length ; char* alloc[0] ; // Compiler specific extension!!! } ; // Allocating a sized block tSizedAlloc* blk = malloc( sizeof(tSizedAlloc) + length ) ; blk->length = length ; // Accessing the size and data information of the block size_t blk_length = blk->length ; char* data = blk->alloc ; 

После прочтения ответа Клацко я сам попытался, и ptr[-1] действительно сохранил фактическую память (обычно больше, чем память, которую мы просили, вероятно, чтобы сэкономить на ошибке сегментации).

 { char *a = malloc(1); printf("%u\n", ((size_t *)a)[-1]); //prints 17 free(a); exit(0); } 

Пытаясь использовать разные размеры, GCC выделяет память следующим образом:

Первоначально выделенная память составляет 17 байт.
Выделенная память по меньшей мере на 5 байтов больше запрашиваемого размера, если запрашивается больше, она выделяет еще 8 байтов.

  • Если размер [0,12], выделенная память – 17.
  • Если размер [13], выделенная память – 25.
  • Если размер [20], выделенная память равна 25.
  • Если размер [21], выделенная память – 33.

Я знаю, что эта нить немного старая, но все же мне есть, что сказать. Существует функция (или макрос, я еще не проверял библиотеку). Malloc_usable_size () – получает размер блока памяти, выделенного из кучи. На странице руководства указано, что он предназначен только для отладки, поскольку он выводит не номер, который вы просили, а номер, который он выделил, который немного больше. Обратите внимание, что это расширение GNU.

С другой стороны, это может даже не понадобиться, потому что я считаю, что для освобождения памяти вам не нужно знать его размер. Просто удалите дескриптор / структуру / структуру, которая отвечает за кусок.

Нестандартным способом является использование _msize() . Использование этой функции сделает ваш код незарегистрированным. Кроме того, документация не очень ясна на том, что она вернет номер, переданный в malloc() или реальный размер блока (может быть больше).

Как сохранить эти данные до разработчика malloc . Чаще всего длина сохраняется непосредственно перед выделенной памятью (то есть, если вы хотите выделить 7 байт, в реальности выделяются 7 + x байтов, где x дополнительных байтов используются для хранения метаданных). Иногда метаданные хранятся до и после выделенной памяти, чтобы проверить наличие повреждений кучи. Но разработчик также может использовать дополнительную структуру данных для хранения метаданных.

Вы можете выделить больше памяти для хранения размера:

 void my_malloc(size_t n,size_t size ) { void *p = malloc( (n * size) + sizeof(size_t) ); if( p == NULL ) return NULL; *( (size_t*)p) = n; return (char*)p + sizeof(size_t); } void my_free(void *p) { free( (char*)p - sizeof(size_t) ); } void my_realloc(void *oldp,size_t new_size) { ... } int main(void) { char *p = my_malloc( 20, 1 ); printf("%lu\n",(long int) ((size_t*)p)[-1] ); return 0; } 

Чтобы ответить на вопрос об удалении [], ранние версии C ++ действительно требовали, чтобы вы вызывали delete [n] и указывали размер времени выполнения, поэтому ему не нужно было его хранить. К сожалению, это поведение было снято как «слишком запутанное».

(Подробнее см. D & E.)