it-swarm.com.ru

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

65
Joel

Нет, получить эту информацию невозможно, если сильно не зависеть от деталей реализации malloc. В частности, malloc может выделить больше байтов, чем вы запрашиваете (например, для эффективности в конкретной архитектуре памяти). Было бы намного лучше изменить код так, чтобы вы явно отслеживали n. Альтернативой является по крайней мере такой же редизайн и гораздо более опасный подход (учитывая, что он нестандартен, злоупотребляет семантикой указателей и будет кошмаром обслуживания для тех, кто придет после вас): сохраните lengthn в адрес 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;

и передавать arrInfos, а не необработанные указатели.

Теперь мы готовим. Но пока вы меняете дизайн, зачем останавливаться на достигнутом? Что вам действительно нужно, так это абстрактный тип данных (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++. Боже, помоги нам всем.

52
Barry Wark

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

16
Steven A. Lowe

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

9
paercebal

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

8
dmityugov

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

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

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

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

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

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

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

4
Claudiu

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

2
David Arno

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

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

2
pm100

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


int size;
struct mystruct *cur;

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

size = cur - myarray;

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

2
quinmars

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

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

1
Greg Hewgill

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

Почему вы не можете хранить объем памяти, который вы выделили?

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

1
Bob Somers

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

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

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

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

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

myMax также ожидает количество элементов в массиве как int. Я получаю это значение, используя sizeof (), чтобы дать мне размеры в байтах массива и первого элемента массива, а затем делю общее количество байтов на количество байтов в каждом элементе. (мы не должны угадывать или жестко кодировать размер int, потому что в одной системе он составляет 2 байта, а в некоторых - как мой 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;
}
1
Wm J

В 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))
0
Jonathon Reinhart