Determinar el tamaño de la memoria asignada dinámicamente en C

10 minutos de lectura

avatar de usuario
s_itbhu

¿Hay alguna forma en C de averiguar el tamaño de la memoria asignada dinámicamente?

Por ejemplo, después

char* p = malloc (100);

¿Hay alguna manera de averiguar el tamaño de la memoria asociada con p?

  • sizeof(char) * … es redundante, como char se garantiza que tiene un tamaño de 1.

    – mk12

    19 de agosto de 2012 a las 4:50


  • @ mk12 Todavía deja más claro lo que está pasando. especialmente cuando se escribe como malloc(100*sizeof(char))que sigue la convención habitual de colocar unidades en el lado derecho de una cantidad.

    – DeprimidoDaniel

    29 de diciembre de 2016 a las 4:10

  • En realidad, ahora prefiero escribir TYPE *ptr = malloc(100 * sizeof *ptr), donde TIPO solo se escribe una vez. Esto garantiza que obtendrá una matriz de 100 elementos, incluso si cambia el TIPO.

    – mk12

    30 de diciembre de 2016 a las 20:41

avatar de usuario
ars

No existe una forma estándar de encontrar esta información. Sin embargo, algunas implementaciones proporcionan funciones como msize para hacer esto. Por ejemplo:

Sin embargo, tenga en cuenta que malloc asignará un mínimo del tamaño solicitado, por lo que debe verificar si la variante msize para su implementación realmente devuelve el tamaño del objeto o la memoria realmente asignada en el montón.

avatar de usuario
Quuxplusone

Todo el mundo que te dice que es imposible es técnicamente correcto (el mejor tipo de correcto).

Por razones de ingeniería, es una mala idea confiar en el subsistema malloc para saber con precisión el tamaño de un bloque asignado. Para convencerte de esto, imagina que estás escribiendo un largo aplicación, con varios asignadores de memoria diferentes; tal vez use libc sin procesar malloc en una parte, pero C++ operator new en otra parte, y luego alguna API de Windows específica en otra parte. Así que tienes todo tipo de void* volando alrededor. Escribiendo una función que pueda trabajar en ninguna de estos void*Es imposible, a menos que de alguna manera puedas saber a partir del valor del puntero de cuál de tus montones proviene.

Por lo tanto, es posible que desee envolver cada puntero en su programa con alguna convención que indique de dónde proviene el puntero (y dónde debe devolverse). Por ejemplo, en C++ lo llamamos std::unique_ptr<void> (para punteros que necesitan ser operator deleted) o std::unique_ptr<void, D> (para punteros que deben devolverse a través de algún otro mecanismo D). Podrías hacer el mismo tipo de cosas en C si quisieras. Y una vez que esté envolviendo punteros en objetos más grandes y seguros de todas formases solo un pequeño paso para struct SizedPtr { void *ptr; size_t size; } y luego nunca más tendrás que preocuparte por el tamaño de una asignación.

Sin embargo.

Hay además buenas razones por las que es posible que desee conocer legítimamente el tamaño subyacente real de una asignación. Por ejemplo, tal vez esté escribiendo una herramienta de creación de perfiles para su aplicación que informará la cantidad real de memoria utilizado por cada subsistema, no solo la cantidad de memoria que el programador pensamiento estaba usando. Si cada una de sus asignaciones de 10 bytes usa secretamente 16 bytes bajo el capó, ¡es bueno saberlo! (Por supuesto, también habrá otros gastos generales, que no está midiendo de esta manera. Pero aún hay otras herramientas para ese trabajo.) O tal vez solo estás investigando el comportamiento de realloc en tu plataforma. O tal vez le gustaría “redondear” la capacidad de una asignación creciente para evitar prematuro reasignaciones en el futuro. Ejemplo:

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

Para obtener el tamaño de la asignación detrás de un puntero no nulo que ha sido devuelto directamente desde libc malloc — no desde un montón personalizado, y sin apuntar al medio de un objeto — puede usar las siguientes API específicas del sistema operativo, que he incluido en una función contenedora “portátil” para mayor comodidad. Si encuentra un sistema común en el que este código no funciona, deje un comentario e intentaré solucionarlo.

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

Probado en:

La mentalidad de C es proporcionar al programador herramientas que lo ayuden con su trabajo, no proporcionar abstracciones que cambien la naturaleza de su trabajo. C también intenta evitar hacer las cosas más fáciles/seguras si esto sucede a expensas del límite de rendimiento.

Ciertas cosas que le gustaría hacer con una región de la memoria solo requieren la ubicación del inicio de la región. Tales cosas incluyen trabajar con cadenas terminadas en cero, manipular la primera norte bytes de la región (si se sabe que la región es al menos tan grande), y así sucesivamente.

Básicamente, hacer un seguimiento de la longitud de una región es un trabajo adicional, y si C lo hiciera automáticamente, a veces lo estaría haciendo innecesariamente.

Muchas funciones de biblioteca (por ejemplo fread()) requieren un puntero al inicio de una región, y también el tamaño de esta región. Si necesita el tamaño de una región, debe realizar un seguimiento de la misma.

Sí, las implementaciones de malloc() generalmente realizan un seguimiento del tamaño de una región, pero pueden hacerlo indirectamente, o redondearlo a algún valor, o no mantenerlo en absoluto. Incluso si lo admiten, encontrar el tamaño de esta manera puede ser lento en comparación con realizar un seguimiento.

Si necesita una estructura de datos que sepa qué tan grande es cada región, C puede hacerlo por usted. Simplemente use una estructura que realice un seguimiento de qué tan grande es la región, así como un puntero a la región.

  • Si bien esta respuesta no responde del todo a la pregunta, agradezco la explicación de lo racional para que tal cosa no exista.

    – Alce revestido

    24 de septiembre de 2013 a las 3:34

  • Si el compilador no tiene ningún registro de qué tan grande es el bloque que mallocó, ¿qué hace en la coincidencia libre?

    – Andrés Lázaro

    13 de febrero de 2015 a las 18:19

  • @AndrewLazarus La implementación de la biblioteca podría tener una forma indirecta de determinarlo, sin agregar nada directamente al bloque, y podría negarse a compartirlo 🙂

    – Kuba no se ha olvidado de Mónica

    16 de agosto de 2015 a las 7:53

avatar de usuario
Tomas_M

Quuxplusone escribió: “Escribir una función que pueda funcionar en cualquiera de estos vacíos es imposible, a menos que de alguna manera pueda saber a partir del valor del puntero de cuál de sus montones proviene”. Determine el tamaño de la memoria asignada dinámicamente en C”

En realidad, en Windows, _msize le brinda el tamaño de memoria asignado a partir del valor del puntero. Si no hay memoria asignada en la dirección, se genera un error.

int main()
{
    char* ptr1 = NULL, * ptr2 = NULL;
    size_t bsz;    
    ptr1 = (char*)malloc(10);
    ptr2 = ptr1;
    bsz = _msize(ptr2);
    ptr1++;
    //bsz = _msize(ptr1);   /* error */
    free(ptr2);

    return 0;
}

Gracias por la colección #define. Aquí está la versión macro.

#define MALLOC(bsz) malloc(bsz)
#define FREE(ptr) do { free(ptr); ptr = NULL; } while(0)
#ifdef __linux__
#include <malloc.h>
#define MSIZE(ptr) malloc_usable_size((void*)ptr)
#elif defined __APPLE__
#include <malloc/malloc.h>
#define MSIZE(ptr) malloc_size(const void *ptr)
#elif defined _WIN32
#include <malloc.h>
#define MSIZE(ptr) _msize(ptr)
#else
#error "unknown system"
#endif

Nota: usando _msize solo funciona para la memoria asignada con calloc, mallocetc. Como se indica en la documentación de Microsoft

La función _msize devuelve el tamaño, en bytes, del bloque de memoria asignado por una llamada a calloc, malloco realloc.

Y lanzará una excepción de lo contrario.

https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/msize?view=vs-2019

comp.lang.c Lista de preguntas frecuentes · Pregunta 7.27

P. Entonces, ¿puedo consultar el malloc paquete para averiguar qué tan grande es un bloque asignado?

R. Desafortunadamente, no existe una forma estándar o portátil. (Algunos compiladores proporcionan extensiones no estándar). Si necesita saberlo, tendrá que realizar un seguimiento de ello usted mismo. (Véase también la pregunta 7.28.)

avatar de usuario
Comunidad

Esta es la mejor manera que he visto para crear un puntero etiquetado para almacenar el tamaño con la dirección. Todas las funciones de puntero seguirían funcionando como se esperaba:

Robado de: https://stackoverflow.com/a/35326444/638848

También puede implementar un contenedor para malloc y agregar etiquetas (como el tamaño asignado y otra metainformación) antes del puntero devuelto por malloc. De hecho, este es el método en el que un compilador de C++ etiqueta objetos con referencias a clases virtuales. Aquí hay un ejemplo de trabajo:

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

La ventaja de este método sobre una estructura con tamaño y puntero.

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

es que solo necesitas reponer el malloc y llamadas gratis. Todas las demás operaciones de puntero no requieren refactorización.

  • también deberías redefinir realloc

    – usuario1251840

    8 de marzo de 2018 a las 9:40

  • @AndrewHenle es un requisito estándar de C que debería ser “El puntero devolvió SI la asignación tiene éxito está adecuadamente alineada para que pueda asignarse a un puntero a cualquier tipo de objeto con un requisito de alineación fundamental y luego usarse para acceder a dicho objeto o a una matriz de dichos objetos en el espacio asignado (hasta que el espacio se desasigna explícitamente ).”

    – kajamita

    24 de mayo de 2021 a las 0:25

  • @kajamite Mi punto es que el método que defiende esta respuesta probablemente descansos ese requisito

    –Andrew Henle

    24 de mayo de 2021 a las 11:34


  • @AndrewHenle No veo cómo es posible que pueda romperlo. La implementación de C devuelve la memoria ya alineada. Todo está bien aquí.

    – kajamita

    4 de junio de 2021 a las 13:34

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad