web-dev-qa-db-fra.com

Déterminer la taille de la mémoire allouée dynamiquement en C

Existe-t-il un moyen en C de connaître la taille de la mémoire allouée dynamiquement? 

Par exemple, après

char* p = malloc (100);

Existe-t-il un moyen de connaître la taille de la mémoire associée à p?

50
s_itbhu

comp.lang.c FAQ list · Question 7.27

Q. Je peux donc interroger le paquetage malloc pour savoir quelle est la taille d'un bloc alloué?

R. Malheureusement, il n'y a pas de moyen standard ou portable. (Certains compilateurs fournissent des extensions non standard.) Si vous avez besoin de savoir, vous devrez le garder vous-même. (Voir aussi question 7.28 .)

41
Alex Reynolds

Il n'y a pas de moyen standard pour trouver cette information. Cependant, certaines implémentations fournissent des fonctions telles que msize. Par exemple:

N'oubliez pas cependant que malloc allouera un minimum de la taille demandée. Vérifiez donc si la variante msize de votre implémentation renvoie effectivement la taille de l'objet ou la mémoire effectivement allouée sur le tas.

42
ars

La mentalité C consiste à fournir au programmeur des outils pour l'aider dans son travail, et non à fournir des abstractions qui changent la nature de son travail. C tente également d'éviter de rendre les choses plus faciles/plus sûres si cela se produit aux dépens de la limite de performance.

Certaines choses que vous aimeriez faire avec une région de mémoire ne nécessitent que l’emplacement du début de la région. Cela implique de travailler avec des chaînes à zéro terminal, de manipuler les premiers n octets de la région (si la région est connue pour être au moins aussi grande), etc.

Fondamentalement, garder la trace de la longueur d’une région représente un travail supplémentaire, et si C le faisait automatiquement, il le ferait parfois inutilement.

De nombreuses fonctions de bibliothèque (par exemple fread()) nécessitent un pointeur sur le début d'une région et la taille de cette région. Si vous avez besoin de la taille d'une région, vous devez en faire le suivi.

Oui, les mises en œuvre de malloc () gardent généralement une trace de la taille d'une région, mais elles peuvent le faire indirectement, ou l'arrondir à une valeur quelconque, ou ne pas la conserver du tout. Même s'ils le supportent, trouver la taille de cette façon peut être lent comparé à le garder vous-même.

Si vous avez besoin d'une structure de données qui connaisse la taille de chaque région, C peut le faire pour vous. Utilisez simplement une structure qui garde la trace de la taille de la région, ainsi qu'un pointeur sur la région.

10
Artelius

Non, la bibliothèque d'exécution C ne fournit pas une telle fonction.

Certaines bibliothèques peuvent fournir des fonctions spécifiques à la plate-forme ou au compilateur pouvant obtenir ces informations, mais la manière de garder trace de ces informations consiste généralement à utiliser une autre variable entière.

7
Greg Hewgill

Voici le meilleur moyen que j'ai vu de créer un pointeur étiqueté pour stocker la taille avec l'adresse. Toutes les fonctions de pointeur fonctionneraient toujours comme prévu:

Volé à: https://stackoverflow.com/a/35326444/638848

Vous pouvez également implémenter un wrapper pour malloc et ajouter gratuitement des balises (comme la taille allouée et d’autres méta-informations) avant le pointeur retourné par malloc. C’est en fait la méthode qu’un compilateur c ++ balise des objets avec des références à des classes virtuelles. En voici un qui fonctionne Exemple:

#include <stdlib.h>
#include <stdio.h>

void * my_malloc(size_t s) 
{
  size_t * ret = malloc(sizeof(size_t) + s);
  *ret = s;
  return &ret[1];
}

void my_free(void * ptr) 
{
  free( (size_t*)ptr - 1);
}

size_t allocated_size(void * ptr) 
{
  return ((size_t*)ptr)[-1];
}

int main(int argc, const char ** argv) {
  int * array = my_malloc(sizeof(int) * 3);
  printf("%u\n", allocated_size(array));
  my_free(array);
  return 0;
}

L'avantage de cette méthode par rapport à une structure de taille et pointeur

 struct pointer
 {
   size_t size;
   void *p;
 };

est-ce que vous avez seulement besoin de remplacer le malloc et les appels gratuits. Tout d'autres opérations de pointeur ne nécessitent pas de refactoring.

4
Leslie Godwin

Comme tout le monde l'a déjà dit: non.

De plus, j'éviterais toujours toutes les fonctions spécifiques au fournisseur, car lorsque vous constatez que vous avez vraiment besoin de les utiliser, c'est généralement le signe que vous vous trompez. Vous devez soit stocker la taille séparément, soit ne pas avoir à la connaître du tout. L'utilisation des fonctions du fournisseur est le moyen le plus rapide de perdre l'un des principaux avantages de l'écriture en C, la portabilité.

3
Enno

Je m'attendrais à ce que cela dépende de la mise en œuvre.
Si vous avez la structure de données d'en-tête, vous pouvez la replacer sur le pointeur et obtenir la taille.

2
nik

Eh bien, maintenant, je sais que cela ne répond pas à votre question précise, mais pensez en dehors de la boîte pour ainsi dire ... Il me semble que vous n’avez probablement pas besoin de savoir. Ok, ok, non, je ne veux pas dire que vous avez une implémentation mauvaise ou non orthodoxe ... je veux dire, c'est que vous avez probablement (sans regarder votre code, je devine) que vous voulez probablement seulement savoir si vos données peuvent y entrer dans la mémoire allouée, si tel est le cas, cette solution pourrait être meilleure. Il ne devrait pas offrir trop de frais généraux et résoudra votre problème "d'adaptation" si c'est effectivement ce que vous gérez:

if ( p != (tmp = realloc(p, required_size)) ) p = tmp;

ou si vous devez conserver l'ancien contenu:

if ( p != (tmp = realloc(p, required_size)) ) memcpy(tmp, p = tmp, required_size);

bien sûr, vous pouvez simplement utiliser:

p = realloc(p, required_size);

et en finir avec ça.

1
Erich Horn

Ce code fonctionnera probablement sur la plupart des installations Windows:

template <class T>
int get_allocated_bytes(T* ptr)
{
 return *((int*)ptr-4);
}

template <class T>
int get_allocated_elements(T* ptr)
{
 return get_allocated_bytes(ptr)/sizeof(T);
}
1
H. Acker

Si vous utilisez malloc, vous ne pouvez pas obtenir la taille.

.__ D'autre part, si vous utilisez l'API du système d'exploitation pour allouer de la mémoire de manière dynamique, comme Windows heap functions , il est alors possible de le faire.

1
Nick Dandoulakis

Cela peut fonctionner, une petite mise à jour dans votre code:

void* inc = (void*) (++p)
size=p-inc;

Mais cela donnera 1, c'est-à-dire la mémoire associée à p si c'est char*. Si c'est int* alors le résultat sera 4.

Il n'y a aucun moyen de connaître l'allocation totale.

1
sarin

Tout le monde vous dit que c'est impossible est techniquement correct (le meilleur type de correct).

Pour des raisons techniques, il est déconseillé de s’appuyer sur le sous-système malloc pour vous indiquer avec précision la taille d’un bloc alloué. Pour vous en convaincre, imaginez que vous écriviez une application grande, avec plusieurs allocateurs de mémoire. Vous utilisez peut-être une bibliothèque brc malloc brute dans une partie, mais C++ operator new dans une autre, puis quelques API Windows dans encore une autre partie. Donc, vous avez toutes sortes de void* volant. Écrire une fonction qui peut fonctionner sur n'importe lequel de ces void*s est impossible, à moins que vous ne sachiez en quelque sorte à partir de la valeur du pointeur de quel pieu il provient.

Donc, vous voudrez peut-être encapsuler chaque pointeur de votre programme avec une convention qui indique d'où vient le pointeur (et où il doit être retourné). Par exemple, en C++, nous appelons cela std::unique_ptr<void> (pour les pointeurs devant être operator delete 'd) ou std::unique_ptr<void, D> (pour les pointeurs devant être renvoyés via un autre mécanisme D). Vous pouvez faire la même chose en C si vous le souhaitez. Et une fois que vous avez encapsulé des pointeurs dans des objets plus gros et plus sûrs en tout cas, il ne reste plus qu'un petit pas à faire pour struct SizedPtr { void *ptr; size_t size; } et vous n'aurez plus jamais à vous soucier de la taille d'une allocation.

Toutefois.

Il y a aussi bonnes raisons pour lesquelles vous pourriez légitimement vouloir connaître la taille réelle sous-jacente d'une allocation. Par exemple, vous écrivez peut-être un outil de profilage pour votre application qui indiquera la quantité réelle de mémoire utilisée par chaque sous-système, et pas seulement la quantité de mémoire que le programmeur pensait il Utilisait. Si chacune de vos attributions de 10 octets utilise secrètement 16 octets sous le capot, c'est bon à savoir! (Bien sûr, il y aura aussi d'autres frais généraux que vous ne mesurerez pas de cette façon. Mais il existe encore d'autres outils pour le travail de {that} _). Ou peut-être êtes-vous simplement en train d'étudier le comportement de realloc sur votre Plate-forme. Ou peut-être aimeriez-vous "arrondir" la capacité d'une allocation croissante pour éviter prématurée réaffectations à l'avenir. Exemple:

SizedPtr round_up(void *p) {
    size_t sz = portable_ish_malloced_size(p);
    void *q = realloc(p, sz);  // for sanitizer-cleanliness
    assert(q != NULL && portable_ish_malloced_size(q) == sz);
    return (SizedPtr){q, sz};
}
bool reserve(VectorOfChar *v, size_t newcap) {
    if (v->sizedptr.size >= newcap) return true;
    char *newdata = realloc(v->sizedptr.ptr, newcap);
    if (newdata == NULL) return false;
    v->sizedptr = round_up(newdata);
    return true;
}

Pour obtenir la taille de l'allocation derrière un pointeur non null qui a été retourné directement à partir de libc malloc - pas d'un tas personnalisé, et ne pointant pas au milieu d'un objet - vous pouvez utiliser le système d'exploitation suivant. API spécifiques, que j'ai regroupées dans une fonction d'emballage "portable-ish" pour plus de commodité. Si vous trouvez un système commun où ce code ne fonctionne pas, veuillez laisser un commentaire et je vais essayer de le réparer!

#if defined(__linux__)
// https://linux.die.net/man/3/malloc_usable_size
#include <malloc.h>
size_t portable_ish_malloced_size(const void *p) {
    return malloc_usable_size((void*)p);
}
#Elif defined(__Apple__)
// https://www.unix.com/man-page/osx/3/malloc_size/
#include <malloc/malloc.h>
size_t portable_ish_malloced_size(const void *p) {
    return malloc_size(p);
}
#Elif defined(_WIN32)
// https://docs.Microsoft.com/en-us/cpp/c-runtime-library/reference/msize
#include <malloc.h>
size_t portable_ish_malloced_size(const void *p) {
    return _msize((void *)p);
}
#else
#error "oops, I don't know this system"
#endif

#include <stdio.h>
#include <stdlib.h>  // for malloc itself

int main() {
    void *p = malloc(42);
    size_t true_length = portable_ish_malloced_size(p);
    printf("%zu\n", true_length);
}

Testé sur:

  • Visual Studio, Win64 - _msize
  • GCC/Clang, glibc, Linux - malloc_usable_size
  • Clang, libc, Mac OS X - malloc_size
  • Clang, jemalloc, Mac OS X - fonctionne dans la pratique mais je n'y ferais pas confiance (mélange silencieusement la malloc de jemalloc et le malloc_size de la libc native)
  • Devrait fonctionner correctement avec jemalloc sous Linux
  • Devrait fonctionner correctement avec dlmalloc sous Linux si compilé sans USE_DL_PREFIX
  • Devrait fonctionner correctement avec tcmalloc partout
0
Quuxplusone