Как я могу получить размер массива из указателя в C? - PullRequest
66 голосов
/ 24 октября 2008

Я выделил «массив» размером 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 ]

52 голосов
/ 24 октября 2008

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

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

16 голосов
/ 24 октября 2008

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

9 голосов
/ 24 октября 2008

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

8 голосов
/ 24 октября 2008

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

4 голосов
/ 24 октября 2008

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

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

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

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

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

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

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

2 голосов
/ 03 сентября 2010

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

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

2 голосов
/ 24 октября 2008

Другие обсуждали пределы простых указателей 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 голосов
/ 24 октября 2008

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


int size;
struct mystruct *cur;

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

size = cur - myarray;

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

2 голосов
/ 24 октября 2008

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

1 голос
/ 03 сентября 2010

Это тест моей рутины. Он устанавливает 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;
}
...