Подтвердить что ты не робот

Если 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() "автоматически" знает, что освободить, когда все, что я даю, это тот же самый указатель? Почему я не могу попасть в волшебство, как программист на C?

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

4b9b3361

Ответ 1

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

Ответ 2

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

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

*((size_t *)ptr - 1)

или возможно:

*((size_t *)ptr - 2)

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

Ответ 3

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

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

Безопасный и портативный метод заключается в том, чтобы зарезервировать первое слово выделения для хранения длины. 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 ;

Ответ 4

Прочитав ответ Klatchko, я сам пробовал его, а 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.

Ответ 5

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

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

Ответ 6

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

Ответ 7

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

Ответ 8

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

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;
}

Ответ 9

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

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