Как я могу получить размер массива из указателя в C?

Я выделил "массив" mystruct размера n следующим образом:

if (NULL == (p = calloc(sizeof(struct mystruct) * n,1))) {
 /* handle error */
}

Позже я получил доступ только к p и больше не имел n. Есть ли способ определить длину массива, заданную только указателем p?

Я полагаю, что это возможно, поскольку free(p) делает именно это. Я знаю, что malloc() отслеживает, сколько памяти он выделил, и почему он знает длину; возможно, есть способ запросить эту информацию? Что-то вроде...

int length = askMallocLibraryHowMuchMemoryWasAlloced(p) / sizeof(mystruct)

Я знаю, что мне нужно просто переработать код, чтобы я знал n, но я бы предпочел, если это возможно. Любые идеи?

Ответ 1

Нет, нет никакой возможности получить эту информацию, не сильно завися от деталей реализации malloc. В частности, malloc может выделять больше байтов, чем вы запрашиваете (например, для эффективности в конкретной архитектуре памяти). Было бы намного лучше перепроектировать ваш код, чтобы вы явно отслеживали n. Альтернативой является, по крайней мере, столько же редизайн и гораздо более опасный подход (учитывая, что он нестандартен, злоупотребляет семантикой указателей и будет кошмаром для тех, кто приходит после вас): сохраните длину n на malloc'd, а затем массив. Тогда было бы:

void *p = calloc(sizeof(struct mystruct) * n + sizeof(unsigned long int),1));
*((unsigned long int*)p) = n;

n теперь сохраняется в *((unsigned long int*)p), а начало вашего массива теперь

void *arr = p+sizeof(unsigned long int);

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

typedef struct { 
  unsigned int n;
  void *arr;
} arrInfo;

и обходите arrInfo вместо исходных указателей.

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

typedef void* arrayInfo;
(arrayInfo)newArrayInfo(unsignd int n, unsigned int itemSize);
(void)deleteArrayInfo(arrayInfo);
(unsigned int)arrayLength(arrayInfo);
(void*)arrayPtr(arrayInfo);
...

Другими словами, ADT - это форма инкапсуляции данных и поведения... другими словами, это примерно так же близко, как вы можете добраться до объектно-ориентированного программирования, используя прямой C. Если вы не застряли на платформе, не имеет компилятора С++, вы также можете запустить целый свиньи и просто использовать STL std::vector.

Там мы задали простой вопрос о C и оказались на С++. Бог поможет всем нам.

Ответ 2

следить за размером массива самостоятельно; free использует цепочку malloc для освобождения выделенного блока, который не обязательно имеет тот же размер, что и запрашиваемый массив

Ответ 3

Просто чтобы подтвердить предыдущие ответы: нет способа узнать, просто изучив указатель, сколько памяти было выделено malloc, который вернул этот указатель.

Что делать, если он работал?

Один пример того, почему это невозможно. Представьте себе код с гипотетической функцией, называемой get_size (void *), которая возвращает память, выделенную для указателя:

typedef struct MyStructTag
{ /* etc. */ } MyStruct ;

void doSomething(MyStruct * p)
{
   /* well... extract the memory allocated? */
   size_t i = get_size(p) ;
   initializeMyStructArray(p, i) ;
}

void doSomethingElse()
{
   MyStruct * s = malloc(sizeof(MyStruct) * 10) ; /* Allocate 10 items */
   doSomething(s) ;
}

Почему, даже если это сработало, все равно это не сработает?

Но проблема такого подхода заключается в том, что в C вы можете играть с арифметикой указателей. Позвольте переписать doSomethingElse():

void doSomethingElse()
{
   MyStruct * s = malloc(sizeof(MyStruct) * 10) ; /* Allocate 10 items */
   MyStruct * s2 = s + 5 ; /* s2 points to the 5th item */
   doSomething(s2) ; /* Oops */
}

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

Заключение

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

Ответ 4

Некоторые компиляторы предоставляют msize() или подобные функции (_msize() и т.д.), которые позволяют делать именно это.

Ответ 5

Могу ли я рекомендовать ужасный способ сделать это?

Выделите все свои массивы следующим образом:

void *blockOfMem = malloc(sizeof(mystruct)*n + sizeof(int));

((int *)blockofMem)[0] = n;
mystruct *structs = (mystruct *)(((int *)blockOfMem) + 1);

Затем вы всегда можете отнести свои массивы к int * и получить доступ к элементу -1st.

Обязательно free этот указатель, а не сам указатель массива!

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

Ответ 6

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

Ответ 7

Для массива указателей вы можете использовать массив с завершающим NULL. Затем длина может быть определена так же, как и для строк. В вашем примере вы можете, возможно, использовать атрибут структуры, чтобы затем пометить конец. Конечно, это зависит от того, есть ли член, который не может быть NULL. Поэтому давайте скажем, что у вас есть имя атрибута, которое необходимо установить для каждой структуры вашего массива, после чего вы можете запросить размер по:


int size;
struct mystruct *cur;

for (cur = myarray; cur->name != NULL; cur++)
    ;

size = cur - myarray;

Btw в вашем примере должен быть calloc (n, sizeof (struct mystruct)).

Ответ 8

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

Если вы должны иметь это поведение, вы можете использовать или написать специализированный распределитель памяти. Простейшей задачей было бы реализовать оболочку вокруг функций stdlib.h. Что-то вроде:

void* my_malloc(size_t s);     /* Calls malloc(s), and if successful stores 
                                  (p,s) in a list of handled blocks */
void my_free(void* p);         /* Removes list entry and calls free(p) */
size_t my_block_size(void* p); /* Looks up p, and returns the stored size */
...

Ответ 9

На самом деле ваш вопрос: "Я могу узнать размер блока данных malloc'd (или calloc'd)". И как говорили другие: нет, не стандартным способом.

Однако для этого существуют пользовательские реализации malloc, например http://dmalloc.com/

Ответ 10

Я не знаю, как это сделать, но я бы предположил, что это будет иметь дело с mucking around in malloc internals, который, как правило, очень и очень плохая идея.

Почему вы не можете сохранить размер выделенной памяти?

EDIT: Если вы знаете, что вам следует переработать код, чтобы вы знали n, сделайте это. Да, возможно, быстро и легко попытаться опросить malloc, но знание n наверняка свести к минимуму путаницу и укрепить дизайн.

Ответ 11

Одна из причин, по которой вы не можете запросить библиотеку malloc, насколько большой блок, заключается в том, что распределитель обычно округляет размер вашего запроса, чтобы выполнить минимальное требование к гранулярности (например, 16 байтов). Поэтому, если вы попросите 5 байтов, вы получите блок размером 16. Если вы должны были взять 16 и разделить на 5, вы получите три элемента, когда вы действительно выделили только один. Для библиотеки malloc потребуется дополнительное пространство для отслеживания того, сколько байтов вы указали в первую очередь, поэтому вам лучше всего следить за этим.

Ответ 12

Это проверка моей сортировки. Он устанавливает 7 переменных для хранения значений float, а затем назначает их массиву, который используется для определения максимального значения.

Магия в вызове myMax:

float mmax = myMax ((float *) & arr, (int) sizeof (arr)/sizeof (arr [0]));

И это было волшебным, не так ли?

myMax ожидает указатель массива float (float *), поэтому я использую & arr, чтобы получить адрес массива, и нарисую его как указатель float.

myMax также ожидает количество элементов в массиве как int. Я получаю это значение с помощью sizeof(), чтобы дать размеры байтов массива и первый элемент массива, а затем делить суммарные байты на количество байтов в каждом элементе. (мы не должны угадывать или жестко кодировать размер int, потому что это 2 байта на некоторой системе и 4 на некоторых, подобных моей OS X Mac, и может быть чем-то другим на других).

ПРИМЕЧАНИЕ. Все это важно, когда ваши данные могут иметь различное количество образцов.

Здесь тестовый код:

#include <stdio.h>

float a, b, c, d, e, f, g;

float myMax(float *apa,int soa){
 int i;
 float max = apa[0];
 for(i=0; i< soa; i++){
  if (apa[i]>max){max=apa[i];}
  printf("on i=%d val is %0.2f max is %0.2f, soa=%d\n",i,apa[i],max,soa);
 }
 return max;
}

int main(void)
{
 a = 2.0;
 b = 1.0;
 c = 4.0;
 d = 3.0;
 e = 7.0;
 f = 9.0;
 g = 5.0;
 float arr[] = {a,b,c,d,e,f,g};

 float mmax = myMax((float *)&arr,(int) sizeof(arr)/sizeof(arr[0]));
 printf("mmax = %0.2f\n",mmax);

 return 0;
}

Ответ 13

В uClibc есть макрос MALLOC_SIZE в malloc.h:

/* The size of a malloc allocation is stored in a size_t word
   MALLOC_HEADER_SIZE bytes prior to the start address of the allocation:

     +--------+---------+-------------------+
     | SIZE   |(unused) | allocation  ...   |
     +--------+---------+-------------------+
     ^ BASE             ^ ADDR
     ^ ADDR - MALLOC_HEADER_SIZE
*/

/* The amount of extra space used by the malloc header.  */
#define MALLOC_HEADER_SIZE          \
  (MALLOC_ALIGNMENT < sizeof (size_t)       \
   ? sizeof (size_t)                \
   : MALLOC_ALIGNMENT)

/* Set up the malloc header, and return the user address of a malloc block. */
#define MALLOC_SETUP(base, size)  \
  (MALLOC_SET_SIZE (base, size), (void *)((char *)base + MALLOC_HEADER_SIZE))
/* Set the size of a malloc allocation, given the base address.  */
#define MALLOC_SET_SIZE(base, size) (*(size_t *)(base) = (size))

/* Return base-address of a malloc allocation, given the user address.  */
#define MALLOC_BASE(addr)   ((void *)((char *)addr - MALLOC_HEADER_SIZE))
/* Return the size of a malloc allocation, given the user address. */
#define MALLOC_SIZE(addr)   (*(size_t *)MALLOC_BASE(addr))