Определить размер динамически выделяемой памяти в C

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

Например, после

char* p = malloc (100);

Есть ли способ узнать размер памяти, связанной с p?


person s_itbhu    schedule 15.08.2009    source источник
comment
sizeof(char) * … является избыточным, так как char гарантированно будет иметь размер 1.   -  person mk12    schedule 19.08.2012
comment
@ mk12 Это все еще проясняет, что происходит. Особенно, когда он записывается как malloc(100*sizeof(char)), что соответствует обычному соглашению о размещении единиц справа от количества.   -  person DepressedDaniel    schedule 29.12.2016
comment
На самом деле, сейчас я предпочитаю писать TYPE *ptr = malloc(100 * sizeof *ptr), где TYPE пишется только один раз. Это гарантирует, что вы получите массив из 100 элементов, даже если вы измените TYPE.   -  person mk12    schedule 30.12.2016


Ответы (15)


Список часто задаваемых вопросов comp.lang.c · Вопрос 7.27 -

В. Итак, могу ли я запросить пакет malloc, чтобы узнать, насколько велик выделенный блок?

A. К сожалению, стандартного или портативного способа не существует. (Некоторые компиляторы предоставляют нестандартные расширения.) Если вам нужно знать, вам придется следить за этим самостоятельно. (См. Также вопрос 7.28.)

person Alex Reynolds    schedule 15.08.2009

Стандартного способа найти эту информацию не существует. Однако в некоторых реализациях для этого есть такие функции, как msize. Например:

Однако имейте в виду, что этот malloc будет выделять минимум запрошенного размера, поэтому вам следует проверить, действительно ли вариант msize для вашей реализации возвращает размер объекта или память, фактически выделенную в куче.

person ars    schedule 15.08.2009

Менталитет C заключается в том, чтобы предоставить программисту инструменты, помогающие ему в работе, а не предоставлять абстракции, которые меняют характер его работы. C также пытается избежать упрощения / безопасности, если это происходит за счет ограничения производительности.

Некоторые вещи, которые вы могли бы сделать с областью памяти, требуют только местоположения начала области. Такие вещи включают в себя работу со строками с завершающим нулем, манипулирование первыми n байтами области (если известно, что область по крайней мере такая большая) и т. Д.

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

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

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

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

person Artelius    schedule 15.08.2009
comment
Хотя этот ответ не совсем отвечает на вопрос, я ценю объяснение рациональности того, чего не существует. - person CoatedMoose; 24.09.2013
comment
Если компилятор не имеет записи о том, насколько большой блок был преобразован, что он делает при сопоставлении свободного места? - person Andrew Lazarus; 13.02.2015
comment
@AndrewLazarus. Реализация библиотеки может иметь косвенный способ ее определения, без непосредственного добавления чего-либо в блок, и она может отказаться делиться им :) - person Kuba hasn't forgotten Monica; 16.08.2015

Вот лучший способ, который я видел, для создания помеченного указателя для хранения размера с адресом. Все функции указателя по-прежнему будут работать должным образом:

Украдено с: https://stackoverflow.com/a/35326444/638848

Вы также можете реализовать оболочку для malloc и бесплатно добавлять теги (например, выделенный размер и другую метаинформацию) перед указателем, возвращаемым malloc. Фактически, это метод, которым компилятор C ++ помечает объекты ссылками на виртуальные классы. Вот один рабочий пример:

#include <stdlib.h>
#include <stdio.h>

void * my_malloc(size_t s) 
{
  size_t * ret = malloc(sizeof(size_t) + s);
  *ret = s;
  return &ret[1];
}

void my_free(void * ptr) 
{
  free( (size_t*)ptr - 1);
}

size_t allocated_size(void * ptr) 
{
  return ((size_t*)ptr)[-1];
}

int main(int argc, const char ** argv) {
  int * array = my_malloc(sizeof(int) * 3);
  printf("%u\n", allocated_size(array));
  my_free(array);
  return 0;
}

Преимущество этого метода перед структурой с размером и указателем

 struct pointer
 {
   size_t size;
   void *p;
 };

в том, что вам нужно только заменить malloc и бесплатные звонки. Все остальные операции с указателями не требуют рефакторинга.

person Leslie Godwin    schedule 20.04.2016
comment
вам также следует переопределить realloc - person user1251840; 08.03.2018
comment
@AndrewHenle это стандартное требование C, которое должно быть. Возвращаемый указатель IF успешное выделение выравнивается соответствующим образом, чтобы его можно было назначить указателю на любой тип объекта с фундаментальным требованием выравнивания, а затем использовать для доступа к такому объекту или массиву таких объектов в выделенном пространстве (до тех пор, пока пространство не будет явно освобождено). - person kajamite; 24.05.2021
comment
@kajamite Я считаю, что метод, который предлагается в этом ответе, вероятно, нарушает это требование. - person Andrew Henle; 24.05.2021
comment
@AndrewHenle Я не понимаю, как это может его сломать. Реализация C возвращает уже выровненную память. Здесь все хорошо. - person kajamite; 04.06.2021
comment
@kajamite Здесь не все хорошо У вас никогда не писал кода ни для чего, кроме x86 и других очень щадящих систем, не так ли? И это даже небезопасно x86 системы. В этом ответе предполагается, что &ret[1] будет соответствующим образом выровнен, но это неверно - нет никаких гарантий, что это так. - person Andrew Henle; 04.06.2021
comment
Другими словами, даже если malloc() возвращает правильно выровненную память, my_malloc() в этом ответе не, потому что он изменяет значение, возвращаемое malloc() - вызывающий код не гарантируется правильно выровненная память. - person Andrew Henle; 04.06.2021

Нет, библиотека времени выполнения C не предоставляет такой функции.

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

person Greg Hewgill    schedule 15.08.2009

Все, кто говорит вам, что это невозможно, технически правильны (лучший вариант правильного).

По техническим причинам полагаться на подсистему malloc для точного определения размера выделенного блока - плохая идея. Чтобы убедиться в этом, представьте, что вы пишете большое приложение с несколькими разными распределителями памяти - возможно, вы используете необработанную libc malloc в одной части, но C ++ operator new в другой части, а затем какую-то конкретную Windows API в еще одной части. Итак, у вас есть все виды void* летающих. Написать функцию, которая может работать с любыми из этих void*, невозможно, если вы не сможете каким-то образом определить по значению указателя, из какой из ваших куч он пришел.

Таким образом, вы можете заключить каждый указатель в своей программе с некоторым соглашением, которое указывает, откуда он появился (и куда его нужно вернуть). Например, в C ++ мы называем это std::unique_ptr<void> (для указателей, которые должны быть operator delete'd) или std::unique_ptr<void, D> (для указателей, которые необходимо вернуть через какой-либо другой механизм D). Вы могли бы сделать то же самое в C, если бы захотели. И как только вы заключите указатели в более крупные и безопасные объекты в любом случае, это всего лишь небольшой шаг до struct SizedPtr { void *ptr; size_t size; }, и тогда вам больше не придется беспокоиться о размере выделения.

Однако.

Есть также веские причины, по которым вы можете законно захотеть узнать фактический базовый размер выделения. Например, возможно, вы пишете инструмент профилирования для своего приложения, который будет сообщать фактический объем памяти, используемый каждой подсистемой, а не только объем памяти, который подумал программист Он пользовался. Если каждое из ваших 10-байтовых распределений тайно использует 16 байтов под капотом, это полезно знать! (Конечно, будут и другие накладные расходы, которые вы не измеряете таким образом. Но есть и другие инструменты для этой работы.) Или, может быть, вы просто исследуете поведение realloc на ваша платформа. Или, может быть, вы хотите «округлить» емкость растущего выделения, чтобы избежать преждевременного перераспределения в будущем. Пример:

SizedPtr round_up(void *p) {
    size_t sz = portable_ish_malloced_size(p);
    void *q = realloc(p, sz);  // for sanitizer-cleanliness
    assert(q != NULL && portable_ish_malloced_size(q) == sz);
    return (SizedPtr){q, sz};
}
bool reserve(VectorOfChar *v, size_t newcap) {
    if (v->sizedptr.size >= newcap) return true;
    char *newdata = realloc(v->sizedptr.ptr, newcap);
    if (newdata == NULL) return false;
    v->sizedptr = round_up(newdata);
    return true;
}

Чтобы получить размер выделения за ненулевым указателем , который был возвращен непосредственно из libc malloc - не из настраиваемой кучи и не указывающий на середину объекта - вы можете использовать следующее API-интерфейсы, специфичные для ОС, которые я для удобства объединил в функцию-оболочку "Portable-ish". Если вы найдете общую систему, в которой этот код не работает, оставьте комментарий, и я постараюсь это исправить!

#if defined(__linux__)
// https://linux.die.net/man/3/malloc_usable_size
#include <malloc.h>
size_t portable_ish_malloced_size(const void *p) {
    return malloc_usable_size((void*)p);
}
#elif defined(__APPLE__)
// https://www.unix.com/man-page/osx/3/malloc_size/
#include <malloc/malloc.h>
size_t portable_ish_malloced_size(const void *p) {
    return malloc_size(p);
}
#elif defined(_WIN32)
// https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/msize
#include <malloc.h>
size_t portable_ish_malloced_size(const void *p) {
    return _msize((void *)p);
}
#else
#error "oops, I don't know this system"
#endif

#include <stdio.h>
#include <stdlib.h>  // for malloc itself

int main() {
    void *p = malloc(42);
    size_t true_length = portable_ish_malloced_size(p);
    printf("%zu\n", true_length);
}

Проверено на:

  • Visual Studio, Win64 - _msize
  • GCC / Clang, glibc, Linux - malloc_usable_size
  • Clang, libc, Mac OS X - malloc_size
  • Clang, jemalloc, Mac OS X - работает на практике, но я бы не стал ему доверять (молча смешивает malloc jemalloc и malloc_size родной libc)
  • Должен нормально работать с jemalloc в Linux
  • Должен нормально работать с dlmalloc в Linux, если скомпилирован без USE_DL_PREFIX
  • Должен нормально работать с tcmalloc везде
person Quuxplusone    schedule 04.02.2018

Как все уже сказали: нет.

Кроме того, я бы всегда избегал здесь всех функций, зависящих от поставщика, потому что, когда вы обнаруживаете, что вам действительно нужно их использовать, это, как правило, признак того, что вы делаете это неправильно. Размер следует либо хранить отдельно, либо вовсе не знать его. Использование функций поставщика - это самый быстрый способ потерять одно из главных преимуществ написания на C - переносимость.

person Enno    schedule 15.08.2009
comment
Я видел несколько проектов, но был только один, где возникла идея, что это должно работать на чем-то другом, кроме того, для чего изначально было разработано. Каждый вовлеченный проект в компании получил задание переписать с нуля по этому поводу. - person Kobor42; 23.05.2013
comment
Значит, тебе повезло. Я работаю над множеством устаревших проектов, и это проблемы, которые, как правило, очень трудно решить. - person Enno; 26.10.2020

Я ожидал, что это будет зависеть от реализации.
Если у вас есть структура данных заголовка, вы можете вернуть ее на указатель и получить размер.

person nik    schedule 15.08.2009
comment
Какая структура данных заголовка? - person nos; 15.08.2009
comment
@nos, если реализация выделила требуемый размер вместе с заголовком mem-manager, возвращающим место после заголовка в качестве блока памяти с выделенным размером. Такой заголовок, скорее всего, будет хранить сам размер. Если определение типа заголовка было известно, выделенный указатель можно было бы переместить назад, чтобы получить доступ к заголовку и его полю. В такой реализации диспетчер памяти сам будет реализовывать операцию free аналогично для минимального обслуживания (и высокого риска). - person nik; 15.08.2009
comment
В зависимости от того, как диспетчер памяти реализует учет операции free, получить размер из указателя может - а может быть - невозможно (однако необходимо знать реализацию). Однако в некоторых реализациях этот примитив может быть указан в API (в этом случае внутренние знания не требуются). - person nik; 15.08.2009

Если вы используете malloc, вы не можете получить размер.

С другой стороны, если вы используете API ОС для динамического выделения памяти, например Windows heap functions, тогда это возможно.

person Nick Dandoulakis    schedule 15.08.2009

Что ж, теперь я знаю, что это не ответ на ваш конкретный вопрос, однако мыслить нестандартно ... Мне приходит в голову, что вам, вероятно, не нужно знать. Хорошо, хорошо, нет, я не имею в виду, что у вас плохая или неортодоксальная реализация ... Я имею в виду, что вы, вероятно (не глядя на свой код, я только предполагаю), вы, вероятно, только хотите знать, подходят ли ваши данные в выделенной памяти, если это так, то это решение может быть лучше. Он не должен предлагать слишком много накладных расходов и решит вашу проблему "подгонки", если это действительно то, с чем вы справляетесь:

if ( p != (tmp = realloc(p, required_size)) ) p = tmp;

или если вам нужно сохранить старое содержимое:

if ( p != (tmp = realloc(p, required_size)) ) memcpy(tmp, p = tmp, required_size);

конечно, вы можете просто использовать:

p = realloc(p, required_size);

и покончить с этим.

person Erich Horn    schedule 12.11.2015

Этот код, вероятно, будет работать в большинстве установок Windows:

template <class T>
int get_allocated_bytes(T* ptr)
{
 return *((int*)ptr-4);
}

template <class T>
int get_allocated_elements(T* ptr)
{
 return get_allocated_bytes(ptr)/sizeof(T);
}
person H. Acker    schedule 31.10.2010
comment
Примечание. Это ответ C ++ на вопрос C. - person chux - Reinstate Monica; 12.02.2014

Quuxplusone писал: «Написать функцию, которая может работать с любым из этих void *, невозможно, если вы не сможете каким-либо образом определить по значению указателя, из какой из ваших куч он пришел». Определить размер динамически выделяемой памяти в C "

Фактически в Windows _msize дает вам размер выделенной памяти из значения указателя. Если по адресу нет выделенной памяти, выдается ошибка.

int main()
{
    char* ptr1 = NULL, * ptr2 = NULL;
    size_t bsz;    
    ptr1 = (char*)malloc(10);
    ptr2 = ptr1;
    bsz = _msize(ptr2);
    ptr1++;
    //bsz = _msize(ptr1);   /* error */
    free(ptr2);

    return 0;
}

Спасибо за коллекцию #define. Вот версия макроса.

#define MALLOC(bsz) malloc(bsz)
#define FREE(ptr) do { free(ptr); ptr = NULL; } while(0)
#ifdef __linux__
#include <malloc.h>
#define MSIZE(ptr) malloc_usable_size((void*)ptr)
#elif defined __APPLE__
#include <malloc/malloc.h>
#define MSIZE(ptr) malloc_size(const void *ptr)
#elif defined _WIN32
#include <malloc.h>
#define MSIZE(ptr) _msize(ptr)
#else
#error "unknown system"
#endif
person Thomas_M    schedule 16.02.2019

Недавно я боролся с визуализацией памяти, которая была доступна для записи (т.е. с использованием функций типа strcat или strcpy сразу после malloc).

Это не должно быть слишком техническим ответом, но он может помочь вам при отладке, так же как и мне.

Вы можете использовать размер, который вы mallocd, в memset, установить произвольное значение для второго параметра (чтобы вы могли его распознать) и использовать указатель, который вы получили от malloc.

Вот так:

char* my_string = (char*) malloc(custom_size * sizeof(char));
if(my_string) { memset(my_string, 1, custom_size); }

Затем вы можете визуализировать в отладчике, как выглядит ваша выделенная память:  введите описание изображения здесь

person bem22    schedule 31.10.2019
comment
Это гарантированно работает только с указателями char *. Любой другой тип сопряжен с проблемами согласования и неопределенным поведением. - person Andrew Henle; 31.10.2019
comment
Да, я согласен с этим. - person bem22; 31.10.2019

Примечание: использование _msize работает только для памяти, выделенной с помощью calloc, malloc и т. Д. Как указано в документации Microsoft

Функция _msize возвращает размер в байтах блока памяти, выделенного вызовом calloc, malloc или realloc.

И в противном случае вызовет исключение.

https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/msize?view=vs-2019

person Peter    schedule 26.05.2020

Это может сработать, небольшое обновление в вашем коде:

void* inc = (void*) (++p)
size=p-inc;

Но в результате будет 1, то есть память, связанная с p, если это char*. Если это int*, то результат будет 4.

Нет никакого способа узнать общее распределение.

person sarin    schedule 29.07.2011