Domanda

Ho assegnato un " array " di mystruct di dimensioni n come questo:

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

In seguito, ho accesso solo a p e non ho più n . C'è un modo per determinare la lunghezza dell'array dato solo il puntatore p ?

Lo immagino deve essere possibile, dal momento che free (p) fa proprio questo. So che malloc () tiene traccia di quanta memoria ha allocato, ed è per questo che conosce la lunghezza; forse c'è un modo per richiedere queste informazioni? Qualcosa come ...

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

So che dovrei semplicemente rielaborare il codice in modo da conoscere n , ma preferirei non farlo. Qualche idea?

È stato utile?

Soluzione

No, non c'è modo di ottenere queste informazioni senza dipendere fortemente dai dettagli di implementazione di malloc . In particolare, malloc può allocare più byte di quelli richiesti (ad es. Per l'efficienza in una particolare architettura di memoria). Sarebbe molto meglio ridisegnare il codice in modo da tenere traccia esplicita di n . L'alternativa è almeno tanto riprogettazione e un approccio molto più pericoloso (dato che è non standard, abusa della semantica dei puntatori e sarà un incubo di manutenzione per quelli che ti seguono): store la lunghezza n all'indirizzo malloc'd, seguito dall'array. L'assegnazione sarebbe quindi:

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

n è ora memorizzato in * ((unsigned long int *) p) e l'inizio dell'array è ora

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

Modifica: Giusto per interpretare l'avvocato del diavolo ... So che queste "soluzioni" tutti richiedono riprogettazioni, ma giochiamo. Naturalmente, la soluzione presentata sopra è solo un'implementazione confusa di una struttura (ben confezionata). Potresti anche definire:

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

e passa arrInfo piuttosto che puntatori non elaborati.

Ora stiamo cucinando. Ma finché stai ridisegnando, perché fermarsi qui? Quello che vuoi veramente è un tipo di dati astratto (ADT). Qualsiasi testo introduttivo per una classe di algoritmi e strutture di dati lo farebbe. Un ADT definisce l'interfaccia pubblica di un tipo di dati ma nasconde l'implementazione di quel tipo di dati. Pertanto, pubblicamente un ADT per un array potrebbe apparire

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

In altre parole, un ADT è una forma di incapsulamento di dati e comportamenti ... in altre parole, si avvicina il più possibile alla programmazione orientata agli oggetti usando la riga C. A meno che non si sia bloccati su una piattaforma che non ha un compilatore C ++, potresti anche andare su tutto il maiale e usare solo un std :: vector STL.

Lì abbiamo fatto una semplice domanda su C e siamo finiti in C ++. Dio ci aiuti tutti.

Altri suggerimenti

tenere traccia delle dimensioni dell'array da soli; free usa la catena malloc per liberare il blocco che è stato allocato, che non ha necessariamente le stesse dimensioni dell'array che hai richiesto

Solo per confermare le risposte precedenti: non c'è modo di sapere, solo studiando un puntatore, quanta memoria è stata allocata da un malloc che ha restituito questo puntatore.

E se funzionasse?

Un esempio del perché questo non è possibile. Immaginiamo il codice con una funzione ipotetica chiamata get_size (void *) che restituisce la memoria allocata per un puntatore:

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

Perché anche se funzionasse, non funzionerebbe comunque?

Ma il problema di questo approccio è che, in C, puoi giocare con l'aritmetica del puntatore. Riscriviamo 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 */
}

Come dovrebbe funzionare get_size, quando hai inviato alla funzione un puntatore valido, ma non quello restituito da malloc. E anche se get_size ha attraversato tutti i problemi per trovare la dimensione (cioè in modo inefficiente), in questo caso restituirebbe un valore che sarebbe sbagliato nel tuo contesto.

Conclusione

Esistono sempre modi per evitare questo problema, e in C puoi sempre scrivere il tuo allocatore, ma, di nuovo, è forse troppo disturbo quando tutto ciò che serve è ricordare quanta memoria è stata allocata.

Alcuni compilatori forniscono funzioni msize () o simili (_msize () ecc.), che ti permettono di fare esattamente questo

Posso raccomandare un modo terribile di farlo?

Allocare tutti i tuoi array come segue:

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

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

Quindi puoi sempre trasmettere i tuoi array a int * e accedere al -1 ° elemento.

Assicurati di liberare quel puntatore, e non il puntatore dell'array stesso!

Inoltre, questo probabilmente causerà terribili bug che ti lasceranno a strapparti i capelli. Forse puoi avvolgere le funzioni di allocazione in chiamate API o qualcosa del genere.

malloc restituirà un blocco di memoria grande almeno quanto richiesto, ma forse più grande. Pertanto, anche se si potesse eseguire una query sulla dimensione del blocco, ciò non darebbe in modo affidabile la dimensione dell'array. Quindi dovrai solo modificare il tuo codice per tenerne traccia da solo.

Per un array di puntatori è possibile utilizzare un array con terminazione NULL. La lunghezza può quindi determinare come è fatto con le stringhe. Nel tuo esempio puoi forse usare un attributo di struttura per contrassegnare e poi terminare. Naturalmente ciò dipende se esiste un membro che non può essere NULL. Supponiamo quindi che tu abbia un nome di attributo, che deve essere impostato per ogni struttura dell'array in cui puoi quindi interrogare la dimensione:


int size;
struct mystruct *cur;

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

size = cur - myarray;

Tra l'altro dovrebbe essere calloc (n, sizeof (struct mystruct)) nel tuo esempio.

Altri hanno discusso i limiti dei semplici puntatori c e le implementazioni di stdlib.h di malloc () . Alcune implementazioni forniscono estensioni che restituiscono la dimensione del blocco allocata che può essere maggiore della dimensione richiesta.

Se devi avere questo comportamento puoi usare o scrivere un allocatore di memoria specializzato. Questa cosa più semplice da fare sarebbe implementare un wrapper attorno alle funzioni stdlib.h . Qualcosa come:

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 */
...

davvero la tua domanda è - " posso scoprire la dimensione di un blocco dati malloc'd (o calloc'd) " ;. E come altri hanno già detto: no, non in modo standard.

Tuttavia ci sono implementazioni personalizzate di malloc che lo fanno - ad esempio http://dmalloc.com/

Non sono a conoscenza di un modo, ma immagino che avrebbe a che fare con il caos negli interni di malloc che è generalmente una pessima idea.

Perché non è possibile archiviare la dimensione della memoria allocata?

EDIT: se sai che dovresti rielaborare il codice in modo da sapere n, beh, fallo. Sì, potrebbe essere facile e veloce provare a sondare malloc ma conoscerne n sicuramente minimizzerebbe la confusione e rafforzerebbe il design.

Uno dei motivi per cui non puoi chiedere alla libreria malloc quanto è grande un blocco, è che l'allocatore di solito arrotonda la dimensione della tua richiesta per soddisfare alcuni requisiti minimi di granularità (ad esempio, 16 byte). Quindi, se chiedi 5 byte, otterrai un blocco di dimensioni 16 indietro. Se dovessi prendere 16 e dividere per 5, otterrai tre elementi se ne assegnassi davvero solo uno. Ci vorrebbe più spazio per la libreria malloc per tenere traccia di quanti byte hai richiesto in primo luogo, quindi è meglio per te tenere traccia di quello tu stesso.

Questo è un test della mia routine di ordinamento. Imposta 7 variabili per contenere i valori float, quindi le assegna a un array, che viene utilizzato per trovare il valore massimo.

La magia è nella chiamata a myMax:

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

E quello era magico, no?

myMax prevede un puntatore di array float (float *), quindi uso & amp; arr per ottenere l'indirizzo dell'array e lo lancio come puntatore float.

myMax prevede anche il numero di elementi nell'array come int. Ottengo quel valore usando sizeof () per darmi le dimensioni in byte dell'array e il primo elemento dell'array, quindi divido i byte totali per il numero di byte in ciascun elemento. (non dovremmo indovinare o codificare le dimensioni di un int perché sono 2 byte su alcuni sistemi e 4 su alcuni come il mio Mac OS X e potrebbero essere qualcos'altro su altri).

NOTA: tutto ciò è importante quando i tuoi dati possono avere un numero variabile di campioni.

Ecco il codice di prova:

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

In uClibc , c'è una macro MALLOC_SIZE 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))
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top