Как я могу получить размер массива из указателя в 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, но я бы предпочел, если это возможно. Есть идеи?

13 ответов


нет, нет никакого способа получить эту информацию без сильно зависит от деталей реализации 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);

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

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-это форма инкапсуляции данных и поведения... другими словами, это примерно так близко, как вы можете добраться до объектно-ориентированного программирования с помощью straight C. Если вы не застряли на платформе, у которой нет компилятора C++, вы можете также пойти на всю свинью и просто использовать STL std::vector.

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


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


просто чтобы подтвердить предыдущие ответы: нет никакого способа узнать, просто изучая указатель, сколько памяти было выделено 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 вы всегда можете написать свой собственный распределитель, но опять же, это, возможно, слишком много проблем, когда все, что вам нужно, это вспомнить, сколько памяти было выделено.


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


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

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

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

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

тогда вы всегда можете привести свои массивы к int * и к -1-й элемент.

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

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


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


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


int size;
struct mystruct *cur;

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

size = cur - myarray;

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


другие обсудили пределы простых указателей 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 */
...

на самом деле ваш вопрос - "я могу узнать размер Танос бы (или calloc б) блок данных". И как говорили другие: нет, не стандартным способом.

однако есть пользовательские реализации malloc, которые это делают-например http://dmalloc.com/


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

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

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


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


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

магия находится в вызове myMax:

поплавок ммах = myMax((типа float *)&Арр(инт) оператор 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;
}

на 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))