wake-up-neo.com

Wie kann ich die Größe eines Arrays aus einem Zeiger in C ermitteln?

Ich habe ein "Array" von mystruct der Größe n wie folgt zugewiesen:

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

Später habe ich nur noch Zugriff auf p und habe nicht mehr n. Gibt es eine Möglichkeit, die Länge des Arrays zu bestimmen, wenn nur der Zeiger p angegeben wird?

Ich denke, dass muss möglich sein muss, da free(p) genau das tut. Ich weiß, malloc() merkt sich an, wie viel Speicherplatz zugewiesen wurde. Deshalb kennt es die Länge. Vielleicht gibt es eine Möglichkeit, diese Informationen abzufragen? So etwas wie...

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

Ich weiß, ich sollte den Code einfach überarbeiten, damit ich n kenne, aber ich möchte es möglichst nicht. Irgendwelche Ideen?

65
Joel

Nein, es gibt keine Möglichkeit, diese Informationen zu erhalten, ohne stark von den Implementierungsdetails von malloc abhängig zu sein. Insbesondere kann malloc mehr Bytes zuordnen, als Sie anfordern (z. B. aus Effizienzgründen in einer bestimmten Speicherarchitektur). Es wäre viel besser, Ihren Code so umzubauen, dass Sie n explizit verfolgen. Die Alternative ist zumindest ebenso viel umgestaltet und ein viel gefährlicherer Ansatz (da er nicht standardisiert ist, die Semantik von Zeigern missbraucht und ein Alptraum der Wartung für diejenigen ist, die nach Ihnen kommen): Speichern Sie die Längen unter die malloc'd Adresse, gefolgt vom Array. Die Zuteilung wäre dann:

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

n ist jetzt unter *((unsigned long int*)p) gespeichert und der Start Ihres Arrays ist jetzt

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

Edit: Nur um Teufels Advokat zu spielen ... Ich weiß, dass diese "Lösungen" alle umgestaltet werden müssen, aber spielen wir es aus. Natürlich ist die oben dargestellte Lösung nur eine hackige Implementierung von a (gut -verpackte) struct. Sie könnten genauso gut definieren:

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

und um arrInfos herumgehen anstatt um rohe Zeiger.

Jetzt kochen wir. Aber solange Sie noch umgestalten, warum sollten Sie hier aufhören? Was Sie wirklich wollen, ist ein abstrakter Datentyp (ADT). Jeder Einführungstext für eine Algorithmus- und Datenstrukturklasse würde dies tun. Ein ADT definiert die öffentliche Schnittstelle eines Datentyps, verbirgt jedoch die Implementierung dieses Datentyps. So kann ein ADT für ein Array öffentlich aussehen

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

Mit anderen Worten, ein ADT ist eine Form der Daten- und Verhaltenskapselung ... mit anderen Worten, es ist ungefähr so ​​nahe wie möglich an der objektorientierten Programmierung mithilfe von Straight C, es sei denn, Sie stecken auf einer Plattform, die dies nicht tut Wenn Sie einen C++ - Compiler haben, können Sie genauso gut einen ganzen Hog ​​gehen und einfach eine STL std::vector verwenden.

Dort haben wir eine einfache Frage zu C gestellt und sind bei C++ gelandet. Gott helfe uns allen.

52
Barry Wark

verfolgen Sie die Array-Größe selbst. free verwendet die Malloc-Kette, um das zugewiesene block freizugeben, das nicht unbedingt die gleiche Größe wie das angeforderte Array hat

16
Steven A. Lowe

Nur um die vorherigen Antworten zu bestätigen: Es gibt keine Möglichkeit, durch das Studium eines Zeigers zu erfahren, wie viel Speicherplatz von einem Malloc zugewiesen wurde, der diesen Zeiger zurückgegeben hat.

Was ist, wenn es funktioniert hat?

Ein Beispiel dafür, warum dies nicht möglich ist. Stellen wir uns den Code mit einer hypothetischen Funktion namens get_size (void *) vor, die den für einen Zeiger zugewiesenen Speicher zurückgibt:

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) ;
}

Warum auch wenn es funktioniert hat, würde es trotzdem nicht funktionieren?

Das Problem dieses Ansatzes ist jedoch, dass Sie in C mit Zeigerarithmetik spielen können. Schreiben wir doSomethingElse () neu:

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

Wie get_size funktionieren soll, da Sie der Funktion einen gültigen Zeiger gesendet haben, nicht jedoch den von malloc zurückgegebenen. Und selbst wenn get_size sich die Mühe machte, die Größe zu ermitteln (d. H. Ineffizient), würde es in diesem Fall einen Wert zurückgeben, der in Ihrem Kontext falsch wäre.

Fazit

Es gibt immer Möglichkeiten, dieses Problem zu vermeiden, und in C können Sie immer Ihren eigenen Zuweiser schreiben. Aber auch hier ist es vielleicht zu viel Mühe, wenn Sie sich nur daran erinnern müssen, wie viel Speicher belegt wurde.

9
paercebal

Einige Compiler bieten Msize () - oder ähnliche Funktionen (_msize () usw.), mit denen Sie genau das tun können

8
dmityugov

Darf ich eine schreckliche Methode empfehlen?

Ordnen Sie alle Ihre Arrays wie folgt zu:

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

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

Dann können Sie Ihre Arrays immer in int * umwandeln und auf das -1st-Element zugreifen.

Stellen Sie sicher, dass der Zeiger free und nicht der Arrayzeiger selbst ist!

Dies führt wahrscheinlich auch zu schrecklichen Fehlern, die dazu führen, dass Sie Ihre Haare ausreißen. Vielleicht können Sie die Zuweisungsfunktionen in API-Aufrufe oder etwas anderes einschließen. 

4
Claudiu

malloc gibt einen Speicherblock zurück, der mindestens so groß ist, wie Sie es angefordert haben, aber möglicherweise größer. Selbst wenn Sie die Blockgröße abfragen könnten, würden Sie Ihre Array-Größe also nicht zuverlässig angeben. Sie müssen also lediglich Ihren Code ändern, um ihn selbst nachverfolgen zu können.

2
David Arno

ihre Frage ist wirklich - "kann ich die Größe eines malloc'd (oder calloc'd) Datenblocks herausfinden" Und wie andere gesagt haben: Nein, nicht auf eine übliche Weise.

Es gibt jedoch benutzerdefinierte Malloc-Implementierungen, die dies tun - zum Beispiel http://dmalloc.com/

2
pm100

Für ein Array von Zeigern können Sie ein NULL-terminiertes Array verwenden. Die Länge kann dann wie bei Strings festgelegt werden. In Ihrem Beispiel können Sie möglicherweise ein Strukturattribut verwenden, um das Ende zu markieren. Das hängt natürlich davon ab, ob es ein Mitglied gibt, das nicht NULL sein kann. Nehmen wir also an, Sie haben einen Attributnamen, der für jede Struktur in Ihrem Array festgelegt werden muss. Anschließend können Sie die Größe abfragen:


int size;
struct mystruct *cur;

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

size = cur - myarray;

Übrigens sollte es in Ihrem Beispiel calloc (n, sizeof (struct mystruct)) sein.

2
quinmars

Andere haben die Grenzen einfacher C-Zeiger und die stdlib.h-Implementierungen von malloc() erörtert. Einige Implementierungen stellen Erweiterungen bereit, die die Blockgröße reserved zurückgeben, die größer als die angeforderte Größe sein kann.

Wenn Sie müssen dieses Verhalten haben, können Sie einen speziellen Speicherzuweiser verwenden oder schreiben. Am einfachsten ist es, einen Wrapper für die stdlib.h-Funktionen zu implementieren. So etwas wie:

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

Einer der Gründe, warum Sie die Malloc-Bibliothek nicht fragen können, wie groß ein Block ist, besteht darin, dass der Zuweiser normalerweise die Größe Ihrer Anforderung aufrundet, um einige Mindestanforderungen an die Granularität (beispielsweise 16 Byte) zu erfüllen. Wenn Sie also nach 5 Bytes fragen, erhalten Sie einen Block der Größe 16 zurück. Wenn Sie 16 nehmen und durch 5 teilen, erhalten Sie drei Elemente, wenn Sie wirklich nur eines zuweisen. Die Malloc-Bibliothek würde zusätzlichen Speicherplatz benötigen, um nachverfolgen zu können, wie viele Bytes Sie ursprünglich angefragt haben. Es ist also am besten, wenn Sie selbst nachverfolgen.

1
Greg Hewgill

Ich kenne keinen Weg, aber ich würde mir vorstellen, dass es sich in mallocs Interna herumschleudern würde, was im Allgemeinen eine sehr, sehr schlechte Idee ist.

Warum können Sie die zugewiesene Speichergröße nicht speichern?

BEARBEITEN: Wenn Sie wissen, dass Sie den Code überarbeiten sollten, damit Sie wissen, n, tun Sie es. Ja, es kann schnell und einfach sein, malloc abzufragen, aber n zu wissen, würde Verwirrung auf ein Minimum reduzieren und das Design stärken.

1
Bob Somers

Dies ist ein Test meiner Sortierroutine. Es werden 7 Variablen eingerichtet, die Float-Werte enthalten, und weisen sie dann einem Array zu, das zur Ermittlung des Maximalwerts verwendet wird.

Der Zauber liegt im Aufruf von myMax:

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

Und das war magisch, nicht wahr? 

myMax erwartet einen Float-Arrayzeiger (float *), daher verwende ich & arr, um die Adresse des Arrays abzurufen und als Float-Pointer umzuwandeln. 

myMax erwartet auch die Anzahl der Elemente im Array als int. Ich bekomme diesen Wert, indem ich sizeof () benutze, um Byte-Größen des Arrays und des ersten Elements des Arrays zu erhalten. Dann dividiere ich die Gesamtbytes durch die Anzahl der Bytes in jedem Element. (Wir sollten die Größe eines Int nicht erraten oder hartcodieren, da dies auf einem System 2 Byte und auf meinem OS X Mac 4 ist und auf anderen möglicherweise etwas anderes sein kann).

HINWEIS: All dies ist wichtig, wenn Ihre Daten eine unterschiedliche Anzahl von Proben haben.

Hier ist der Testcode:

#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

In uClibc gibt es ein MALLOC_SIZE-Makro in 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