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

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

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) знание от?

44
задан Chris Cooper 17 April 2010 в 16:54
поделиться

7 ответов

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

31
ответ дан 26 November 2019 в 22:08
поделиться

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

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

*((size_t *)ptr - 1)

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

*((size_t *)ptr - 2)

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

19
ответ дан 26 November 2019 в 22:08
поделиться

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

3
ответ дан 26 November 2019 в 22:08
поделиться

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

2
ответ дан 26 November 2019 в 22:08
поделиться

Прочитав ответ Клатчко , я сам попробовал, и 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
ответ дан 26 November 2019 в 22:08
поделиться

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

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

Безопасный и переносимый способ - зарезервировать первое слово выделения для хранения длины. 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 ;
8
ответ дан 26 November 2019 в 22:08
поделиться

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

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;
}
1
ответ дан 26 November 2019 в 22:08
поделиться
Другие вопросы по тегам:

Похожие вопросы: