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

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

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() получает свои бесплатные (хар-хар) знания?

Ответы [ 9 ]

30 голосов
/ 16 апреля 2010

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

18 голосов
/ 16 апреля 2010

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

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

*((size_t *)ptr - 1)

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

*((size_t *)ptr - 2)

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

8 голосов
/ 16 апреля 2010

Прочитав ответ Клатчко , я сам попробовал его и 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.
8 голосов
/ 16 апреля 2010

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

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

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

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

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

3 голосов
/ 16 апреля 2010

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

2 голосов
/ 16 апреля 2010

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

1 голос
/ 01 июня 2010

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

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;
}
0 голосов
/ 01 января 2011

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

(подробности см. В D & E).

...