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

StackOverflow https://stackoverflow.com/questions/232691

Вопрос

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

Это было полезно?

Решение

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

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

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

Другие советы

самостоятельно следите за размером массива; free использует цепочку 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-му элементу.

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

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

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

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


int size;
struct mystruct *cur;

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

size = cur - myarray;

Кстати, в вашем примере это должно быть 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 */
...

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

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

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

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

РЕДАКТИРОВАТЬ: Если вы знаете, что вы должны переработать код, чтобы вы знали, ну, сделайте это. Да, попытка опроса malloc может быть быстрой и простой, но знание n наверняка сведет к минимуму путаницу и усилит дизайн.

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

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

Волшебство в вызове myMax:

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

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

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

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

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

Вот код теста:

#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))
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top