web-dev-qa-db-fra.com

Comment puis-je obtenir la taille d'un tableau à partir d'un pointeur en C?

J'ai alloué un "tableau" de mystruct de taille n comme ceci:

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

Plus tard, je n'ai accès qu'à p et je n'ai plus n. Existe-t-il un moyen de déterminer la longueur du tableau à l'aide du pointeur p?

Je suppose que doit être possible, puisque free(p) fait justement cela. Je sais que malloc() note la quantité de mémoire allouée et c’est pourquoi il connaît la longueur; peut-être il y a un moyen de demander cette information? Quelque chose comme...

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

Je sais que je devrais simplement retravailler le code afin de connaître n, mais je préférerais ne pas le faire si possible. Des idées?

65
Joel

Non, il n’ya aucun moyen d’obtenir cette information sans dépendre fortement des détails d’implémentation de malloc. En particulier, malloc peut allouer plus d'octets que vous n'en demandez (par exemple, pour améliorer l'efficacité d'une architecture de mémoire particulière). Il serait bien préférable de modifier votre code afin de garder une trace explicite de n. L'alternative est au moins autant de refonte et une approche beaucoup plus dangereuse (étant donné que c'est non standard, abuse de la sémantique des pointeurs, et sera un cauchemar de maintenance pour ceux qui viennent après vous): stockez la longueurn à l’adresse malloc'd, suivie du tableau. L'allocation serait alors:

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

n est maintenant stocké dans *((unsigned long int*)p) et le début de votre tableau est maintenant

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

Edit: Juste pour jouer l'avocat du diable ... Je sais que ces "solutions" nécessitent toutes une refonte, mais jouons-en. Bien sûr, la solution présentée ci-dessus n’est qu’une implémentation simpliste d’une structure (bien emballée). Vous pourriez aussi bien définir:

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

et faites circuler arrInfos plutôt que des pointeurs bruts.

Maintenant on cuisine. Mais tant que vous modifiez la conception, pourquoi vous arrêter ici? Ce que vous voulez vraiment, c'est un type de données abstrait (ADT). Tout texte d'introduction pour une classe d'algorithmes et de structures de données le ferait. Un ADT définit l'interface publique d'un type de données mais masque l'implémentation de ce type de données. Ainsi, publiquement un ADT pour un tableau pourrait ressembler à

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

En d'autres termes, un ADT est une forme d'encapsulation de données et de comportements ... En d'autres termes, il est aussi proche que possible de la programmation orientée objet utilisant un simple C, à moins que vous ne soyez bloqué sur une plateforme qui ne fonctionne pas Si vous avez un compilateur C++, vous pouvez aussi bien vous contenter d’utiliser un STL std::vector.

Là, nous avons pris une simple question sur C et abouti à C++. Dieu nous aide tous.

52
Barry Wark

gardez une trace de la taille du tableau vous-même; free utilise la chaîne malloc pour libérer le bloc block alloué, qui n'a pas nécessairement la même taille que le tableau demandé

16
Steven A. Lowe

Juste pour confirmer les réponses précédentes: Il n’ya aucun moyen de savoir, simplement en étudiant un pointeur, combien de mémoire a été allouée par un malloc qui a renvoyé ce pointeur.

Et si cela fonctionnait?

Un exemple de pourquoi cela n’est pas possible. Imaginons le code avec une fonction hypothétique appelée get_size (void *) qui renvoie la mémoire allouée pour un pointeur:

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

Pourquoi même si cela fonctionnait, cela ne fonctionnerait quand même pas?

Mais le problème de cette approche est qu’en C, vous pouvez jouer avec l’arithmétique par pointeur. Réécrivons 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 */
}

Comment get_size est censé fonctionner, puisque vous avez envoyé à la fonction un pointeur valide, mais pas celui renvoyé par malloc. Et même si get_size rencontrait tous les problèmes pour trouver la taille (c'est-à-dire de manière inefficace), il renverrait, dans ce cas, une valeur qui serait fausse dans votre contexte.

Conclusion

Il y a toujours moyen d'éviter ce problème, et en C, vous pouvez toujours écrire votre propre allocateur, mais encore une fois, c'est peut-être trop gênant lorsque vous avez juste besoin de vous rappeler la quantité de mémoire allouée.

9
paercebal

Certains compilateurs fournissent msize () ou des fonctions similaires (_msize (), etc.), qui vous permettent de faire exactement cela

8
dmityugov

Puis-je recommander un moyen terrible de le faire?

Allouez tous vos tableaux comme suit:

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

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

Ensuite, vous pouvez toujours convertir vos tableaux en int * et accéder au premier élément.

Assurez-vous de free ce pointeur et non du pointeur de tableau lui-même!

En outre, cela causera probablement de terribles insectes qui vous laisseront vous arracher les cheveux. Peut-être que vous pouvez envelopper les fonctions d’allocation dans des appels d’API ou quelque chose de ce genre. 

4
Claudiu

malloc retournera un bloc de mémoire au moins aussi grand que vous avez demandé, mais peut-être plus gros. Ainsi, même si vous pouviez interroger la taille du bloc, cela ne vous donnerait pas de manière fiable la taille de votre tableau. Il vous suffira donc de modifier votre code pour le garder vous-même.

2
David Arno

en réalité, votre question est: "puis-je connaître la taille d'un bloc de données en malloc'd (ou calloc'd)". Et comme d'autres l'ont dit: non, pas de manière standard.

Cependant, il existe des implémentations malloc personnalisées qui le font - par exemple http://dmalloc.com/

2
pm100

Pour un tableau de pointeurs, vous pouvez utiliser un tableau terminé par NULL. La longueur peut alors être déterminée comme si elle était faite avec des chaînes. Dans votre exemple, vous pouvez peut-être utiliser un attribut de structure pour marquer puis terminer. Bien sûr, cela dépend si un membre ne peut pas être NULL. Disons donc que vous avez un nom d'attribut, qui doit être défini pour chaque structure de votre tableau, vous pouvez ensuite interroger la taille en


int size;
struct mystruct *cur;

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

size = cur - myarray;

Btw il devrait être calloc (n, sizeof (struct mystruct)) dans votre exemple.

2
quinmars

D'autres ont abordé les limites des pointeurs en clair et des implémentations stdlib.h de malloc(). Certaines implémentations fournissent des extensions qui renvoient la taille de bloc allouée qui peut être supérieure à la taille requise.

Si vous devez avoir ce comportement, vous pouvez utiliser ou écrire un allocateur de mémoire spécialisé. La chose la plus simple à faire serait d’implémenter un wrapper autour des fonctions stdlib.h. Quelque chose comme:

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

Une des raisons pour lesquelles vous ne pouvez pas demander à la bibliothèque malloc quelle est la taille d'un bloc est que l'allocateur arrondit généralement la taille de votre demande afin de respecter une certaine granularité minimale (par exemple, 16 octets). Donc, si vous demandez 5 octets, vous aurez un bloc de taille 16 en retour. Si vous deviez prendre 16 et diviser par 5, vous obtiendriez trois éléments lorsque vous n'en attribuez qu'un. La bibliothèque malloc prendrait un espace supplémentaire pour garder en mémoire le nombre d'octets que vous avez demandé, il est donc préférable que vous en gardiez la trace vous-même.

1
Greg Hewgill

Je ne suis pas au courant d'une solution, mais j'imagine que cela réglerait les problèmes de déformation des internes de Malloc, ce qui est généralement une très, très mauvaise idée.

Pourquoi ne pouvez-vous pas stocker la taille de mémoire allouée?

EDIT: Si vous savez que vous devez retravailler le code pour savoir n, eh bien, faites-le. Oui, il serait peut-être rapide et facile d’enquêter sur malloc, mais le fait de connaître n serait de nature à réduire au minimum la confusion et à renforcer la conception.

1
Bob Somers

Ceci est un test de ma routine de tri. Il configure 7 variables pour contenir des valeurs flottantes, puis les assigne à un tableau, qui est utilisé pour rechercher la valeur maximale.

La magie est dans l'appel à myMax:

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

Et c'était magique, n'est-ce pas? 

myMax attend un pointeur de tableau float (float *), donc j'utilise & arr pour obtenir l'adresse du tableau et le transforme en pointeur float. 

myMax attend également le nombre d'éléments du tableau sous la forme d'un entier. J'obtiens cette valeur en utilisant sizeof () pour me donner la taille en octets du tableau et du premier élément du tableau, puis divise le nombre total d'octets par le nombre d'octets dans chaque élément. (Nous ne devrions pas deviner ou coder en dur la taille d'un int car il s'agit de 2 octets sur un système et de 4 sur certains comme mon Mac OS X, et pourrait être quelque chose d'autre sur d'autres).

REMARQUE: tout cela est important lorsque vos données peuvent contenir un nombre d'échantillons différent.

Voici le code de test:

#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

Dans uClibc , il existe une macro MALLOC_SIZE dans 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